generated from bastian/boilerplate
mvp
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
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 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user