diff --git a/src/app/shared/nodes/generic-node/generic-node.spec.ts b/src/app/shared/nodes/generic-node/generic-node.spec.ts index 94ab650..d821b0a 100644 --- a/src/app/shared/nodes/generic-node/generic-node.spec.ts +++ b/src/app/shared/nodes/generic-node/generic-node.spec.ts @@ -127,6 +127,96 @@ describe('GenericNodeComponent', () => { }); }); + describe('the array item modal, as it behaves today', () => { + /** + * Characterisation tests. This round trip - schema to dialog fields and back to an object - is + * the machinery an optional-group modal wants to reuse, and nothing covered it: the dialog mock + * resolved null, so no test ever reached the builder or the parser. These pin what it does now, + * so extracting it cannot change it by accident. + */ + const itemSchema = { + type: 'object', + required: ['name', 'weight'], + properties: { + name: { type: 'string', 'x-ui-label': 'Skill name' }, + weight: { type: 'integer' }, + enabled: { type: 'boolean' } + } + }; + + function withArrayField() { + const component = fixture.componentInstance as any; + component.arrayFieldDefinitions = [{ + path: 'skills', + label: 'Skills', + itemSchema, + uniqueBy: null, + ui: { structural: false, visibleWhen: [], enabledWhen: [] } + }]; + return component; + } + + it('builds one dialog field per item property, honouring labels and types', async () => { + const component = withArrayField(); + const open = TestBed.inject(NodeSettingsDialogService).open as ReturnType; + open.mockResolvedValue(null); + + await component.addArrayItem('skills'); + + const dialog = open.mock.calls.at(-1)?.[0]; + expect(dialog.fields.map((field: any) => [field.key, field.type])).toEqual([ + ['name', 'text'], ['weight', 'text'], ['enabled', 'checkbox'] + ]); + expect(dialog.fields[0].label).toBe('Skill name'); + }); + + it('writes the parsed item into the array', async () => { + const component = withArrayField(); + const open = TestBed.inject(NodeSettingsDialogService).open as ReturnType; + open.mockResolvedValue({ name: 'summarise', weight: '3', enabled: true }); + + await component.addArrayItem('skills'); + + const config = component.ensureBlockConfiguration(); + expect(config['skills']).toEqual([{ name: 'summarise', weight: 3, enabled: true }]); + }); + + it('turns an emptied required number into 0, which is what it has always done', async () => { + // Not an endorsement: it is the behaviour a refactor must not change silently. The optional + // case is the one that has to differ, and it differs deliberately. + const component = withArrayField(); + const open = TestBed.inject(NodeSettingsDialogService).open as ReturnType; + open.mockResolvedValue({ name: 'x', weight: '', enabled: false }); + + await component.addArrayItem('skills'); + + expect(component.ensureBlockConfiguration()['skills'][0].weight).toBe(0); + }); + + it('edits an existing item in place rather than appending', async () => { + const component = withArrayField(); + const config = component.ensureBlockConfiguration(); + config['skills'] = [{ name: 'first', weight: 1, enabled: false }]; + const open = TestBed.inject(NodeSettingsDialogService).open as ReturnType; + open.mockResolvedValue({ name: 'renamed', weight: '2', enabled: true }); + + await component.editArrayItem('skills', 0); + + expect(config['skills']).toEqual([{ name: 'renamed', weight: 2, enabled: true }]); + }); + + it('leaves the array untouched when the dialog is cancelled', async () => { + const component = withArrayField(); + const config = component.ensureBlockConfiguration(); + config['skills'] = [{ name: 'first', weight: 1, enabled: false }]; + (TestBed.inject(NodeSettingsDialogService).open as ReturnType).mockResolvedValue(null); + + await component.addArrayItem('skills'); + + expect(config['skills']).toEqual([{ name: 'first', weight: 1, enabled: false }]); + }); + }); + it('should create', () => { expect(component).toBeTruthy(); });