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 = { 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> = []; const api = { furnitureRequirements: ( _id: string, query: Record, ) => { 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> = []; const api = { furnitureRequirements: ( _id: string, query: Record, ) => { 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 }, ]); }); });