generated from bastian/boilerplate
305 lines
9.7 KiB
TypeScript
305 lines
9.7 KiB
TypeScript
import { provideHttpClient } from '@angular/common/http';
|
|
import { TestBed } from '@angular/core/testing';
|
|
import { of } from 'rxjs';
|
|
import { vi } from 'vitest';
|
|
import { AuthService } from '../../core/auth.service';
|
|
import { FurnitureGridComponent } from './furniture-grid.component';
|
|
import type {
|
|
FurnitureOption,
|
|
FurnitureRequirement,
|
|
FurnitureScenario,
|
|
PageResult,
|
|
} from './hauspilot-api.service';
|
|
import { HauspilotApiService } from './hauspilot-api.service';
|
|
|
|
const requirement: FurnitureRequirement = {
|
|
id: 'requirement-1',
|
|
version: 2,
|
|
createdAt: '2026-01-01',
|
|
updatedAt: '2026-01-02',
|
|
projectId: 'project-1',
|
|
roomId: 'room-1',
|
|
name: 'Sofa',
|
|
description: null,
|
|
category: 'seating',
|
|
priority: 'high',
|
|
requiredQuantity: 1,
|
|
status: 'decision_open',
|
|
responsibleUserId: null,
|
|
maximumBudget: '2000.00',
|
|
sortOrder: 0,
|
|
options: [],
|
|
optionCount: 0,
|
|
};
|
|
const page: PageResult<FurnitureRequirement> = {
|
|
items: [requirement],
|
|
page: 1,
|
|
pageSize: 50,
|
|
totalItems: 1,
|
|
totalPages: 1,
|
|
};
|
|
|
|
describe('FurnitureGridComponent', () => {
|
|
it('uses stable database IDs and passes paging and room filters to the API', async () => {
|
|
const calls: Array<Record<string, string | number | boolean | readonly string[]>> = [];
|
|
const api = {
|
|
furnitureRequirements: (
|
|
_id: string,
|
|
query: Record<string, string | number | boolean | readonly string[]>,
|
|
) => {
|
|
calls.push(query);
|
|
return of(page);
|
|
},
|
|
furnitureProjectOptions: () => of({ ...page, items: [] }),
|
|
};
|
|
await TestBed.configureTestingModule({
|
|
imports: [FurnitureGridComponent],
|
|
providers: [
|
|
provideHttpClient(),
|
|
{ provide: HauspilotApiService, useValue: api },
|
|
{ provide: AuthService, useValue: { user: () => ({ id: 'user-1' }) } },
|
|
],
|
|
}).compileComponents();
|
|
const component = TestBed.createComponent(FurnitureGridComponent).componentInstance;
|
|
component.projectId = 'project-1';
|
|
component.roomId = 'room-1';
|
|
component.load(2);
|
|
expect(component.getRowId({ data: requirement })).toBe('requirement-1');
|
|
expect(calls[0]).toMatchObject({ page: 2, pageSize: 50, roomId: 'room-1' });
|
|
});
|
|
|
|
it('debounces search requests', () => {
|
|
vi.useFakeTimers();
|
|
let calls = 0;
|
|
const api = {
|
|
furnitureRequirements: () => {
|
|
calls += 1;
|
|
return of(page);
|
|
},
|
|
furnitureProjectOptions: () => of({ ...page, items: [] }),
|
|
};
|
|
TestBed.configureTestingModule({
|
|
imports: [FurnitureGridComponent],
|
|
providers: [
|
|
provideHttpClient(),
|
|
{ provide: HauspilotApiService, useValue: api },
|
|
{ provide: AuthService, useValue: { user: () => ({ id: 'user-1' }) } },
|
|
],
|
|
});
|
|
const component = TestBed.createComponent(FurnitureGridComponent).componentInstance;
|
|
component.projectId = 'project-1';
|
|
component.searchChanges.next('So');
|
|
vi.advanceTimersByTime(150);
|
|
component.searchChanges.next('Sofa');
|
|
vi.advanceTimersByTime(299);
|
|
expect(calls).toBe(0);
|
|
vi.advanceTimersByTime(1);
|
|
expect(calls).toBe(1);
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('does not expose editable requirement cells to readers', async () => {
|
|
await TestBed.configureTestingModule({
|
|
imports: [FurnitureGridComponent],
|
|
providers: [
|
|
provideHttpClient(),
|
|
{
|
|
provide: HauspilotApiService,
|
|
useValue: {
|
|
furnitureRequirements: () => of(page),
|
|
furnitureProjectOptions: () => of({ ...page, items: [] }),
|
|
},
|
|
},
|
|
{ provide: AuthService, useValue: { user: () => ({ id: 'reader-1' }) } },
|
|
],
|
|
}).compileComponents();
|
|
const component = TestBed.createComponent(FurnitureGridComponent).componentInstance;
|
|
component.canEdit = false;
|
|
component.ngOnChanges();
|
|
expect(component.columns().filter((column) => column.editable === true)).toHaveLength(0);
|
|
});
|
|
|
|
it('uses select editors and readable icon labels for priority and status', async () => {
|
|
await TestBed.configureTestingModule({
|
|
imports: [FurnitureGridComponent],
|
|
providers: [
|
|
provideHttpClient(),
|
|
{
|
|
provide: HauspilotApiService,
|
|
useValue: {
|
|
furnitureRequirements: () => of(page),
|
|
furnitureProjectOptions: () => of({ ...page, items: [] }),
|
|
},
|
|
},
|
|
{ provide: AuthService, useValue: { user: () => ({ id: 'editor-1' }) } },
|
|
],
|
|
}).compileComponents();
|
|
const component = TestBed.createComponent(FurnitureGridComponent).componentInstance;
|
|
component.canEdit = true;
|
|
component.ngOnChanges();
|
|
const priority = component.columns().find((column) => column.field === 'priority');
|
|
const status = component.columns().find((column) => column.field === 'status');
|
|
expect(priority?.cellEditor).toBe('agSelectCellEditor');
|
|
expect(status?.cellEditor).toBe('agSelectCellEditor');
|
|
expect(priority?.cellEditorParams).toMatchObject({
|
|
values: ['optional', 'low', 'normal', 'high', 'essential'],
|
|
});
|
|
expect(status?.cellEditorParams).toMatchObject({
|
|
values: [
|
|
'identified',
|
|
'research',
|
|
'has_options',
|
|
'decision_open',
|
|
'selected',
|
|
'ordered',
|
|
'partially_delivered',
|
|
'delivered',
|
|
'assembled',
|
|
'omitted',
|
|
],
|
|
});
|
|
expect(
|
|
typeof priority?.valueFormatter === 'function'
|
|
? priority.valueFormatter({ value: 'essential' } as never)
|
|
: '',
|
|
).toBe('◆ Unverzichtbar');
|
|
expect(
|
|
typeof status?.valueFormatter === 'function'
|
|
? status.valueFormatter({ value: 'decision_open' } as never)
|
|
: '',
|
|
).toBe('? Entscheidung offen');
|
|
});
|
|
|
|
it('falls back to a supported server sort when persisted grid state is invalid', async () => {
|
|
const calls: Array<Record<string, string | number | boolean | readonly string[]>> = [];
|
|
const api = {
|
|
furnitureRequirements: (
|
|
_id: string,
|
|
query: Record<string, string | number | boolean | readonly string[]>,
|
|
) => {
|
|
calls.push(query);
|
|
return of(page);
|
|
},
|
|
furnitureProjectOptions: () => of({ ...page, items: [] }),
|
|
};
|
|
await TestBed.configureTestingModule({
|
|
imports: [FurnitureGridComponent],
|
|
providers: [
|
|
provideHttpClient(),
|
|
{ provide: HauspilotApiService, useValue: api },
|
|
{ provide: AuthService, useValue: { user: () => ({ id: 'user-1' }) } },
|
|
],
|
|
}).compileComponents();
|
|
const component = TestBed.createComponent(FurnitureGridComponent).componentInstance;
|
|
component.projectId = 'project-1';
|
|
Object.defineProperty(component, 'gridApi', {
|
|
value: { getColumnState: () => [{ colId: 'optionCount', sort: 'asc' }] },
|
|
});
|
|
component.load(1);
|
|
expect(calls[0]?.['sortBy']).toBe('sortOrder');
|
|
});
|
|
|
|
it('assigns an option to a scenario through the existing selection endpoint', async () => {
|
|
const option: FurnitureOption = {
|
|
id: 'option-1',
|
|
version: 1,
|
|
createdAt: '2026-01-01',
|
|
updatedAt: '2026-01-01',
|
|
projectId: 'project-1',
|
|
requirementId: requirement.id,
|
|
name: 'Sofa Wunsch',
|
|
manufacturer: null,
|
|
model: null,
|
|
description: null,
|
|
retailer: null,
|
|
productUrl: null,
|
|
articleNumber: null,
|
|
unitPrice: '1000.00',
|
|
originalPrice: null,
|
|
shippingCost: '0.00',
|
|
additionalCost: '0.00',
|
|
discount: '0.00',
|
|
totalPrice: '1000.00',
|
|
currency: 'EUR',
|
|
quantity: 1,
|
|
width: null,
|
|
height: null,
|
|
depth: null,
|
|
weight: null,
|
|
color: null,
|
|
material: null,
|
|
deliveryDays: null,
|
|
expectedDeliveryDate: null,
|
|
availability: 'available',
|
|
favorite: true,
|
|
currentlySelected: false,
|
|
status: 'favorite',
|
|
notes: null,
|
|
budgetCategoryId: null,
|
|
existingItem: false,
|
|
movingCost: '0.00',
|
|
refurbishmentCost: '0.00',
|
|
deliveryStatus: 'not_ordered',
|
|
deliveredQuantity: 0,
|
|
orderNumber: null,
|
|
orderedAt: null,
|
|
};
|
|
const scenario: FurnitureScenario = {
|
|
id: 'scenario-1',
|
|
version: 3,
|
|
createdAt: '2026-01-01',
|
|
updatedAt: '2026-01-01',
|
|
projectId: 'project-1',
|
|
name: 'Wunsch',
|
|
description: null,
|
|
type: 'preferred',
|
|
status: 'active',
|
|
isDefault: true,
|
|
total: '0.00',
|
|
selectedRequirements: 0,
|
|
openRequirements: 1,
|
|
byRoom: {},
|
|
selections: [],
|
|
};
|
|
const updateSelections = vi.fn(() =>
|
|
of({
|
|
...scenario,
|
|
version: 4,
|
|
total: option.totalPrice,
|
|
selections: [
|
|
{ requirementId: requirement.id, optionId: option.id, quantity: option.quantity },
|
|
],
|
|
}),
|
|
);
|
|
await TestBed.configureTestingModule({
|
|
imports: [FurnitureGridComponent],
|
|
providers: [
|
|
provideHttpClient(),
|
|
{
|
|
provide: HauspilotApiService,
|
|
useValue: {
|
|
furnitureRequirements: () => of(page),
|
|
furnitureProjectOptions: () => of({ ...page, items: [] }),
|
|
updateFurnitureScenarioSelections: updateSelections,
|
|
},
|
|
},
|
|
{ provide: AuthService, useValue: { user: () => ({ id: 'editor-1' }) } },
|
|
],
|
|
}).compileComponents();
|
|
const component = TestBed.createComponent(FurnitureGridComponent).componentInstance;
|
|
component.projectId = 'project-1';
|
|
component.canEdit = true;
|
|
component.scenarios = [scenario];
|
|
component.cellChanged({
|
|
oldValue: '',
|
|
newValue: option.id,
|
|
data: { ...requirement, options: [option] },
|
|
column: { getColId: () => `scenario:${scenario.id}` },
|
|
node: { setDataValue: vi.fn() },
|
|
} as never);
|
|
expect(updateSelections).toHaveBeenCalledWith('project-1', scenario, [
|
|
{ requirementId: requirement.id, optionId: option.id, quantity: 1 },
|
|
]);
|
|
});
|
|
});
|