import { TestBed } from '@angular/core/testing'; import { vi } from 'vitest'; import { EditableExecutionInput, TaskExecutionInputsPanelComponent } from './task-execution-inputs-panel'; function makeInput(overrides: Partial = {}): EditableExecutionInput { return { key: 'global:role', scope: 'global', nodeId: null, inputName: 'role', title: 'Flow', subtitle: 'role', type: 'TEXT', multiple: false, value: '', provided: false, ...overrides }; } async function build(inputs: EditableExecutionInput[], options: { pendingKeys?: string[]; saving?: Record; readOnly?: boolean; } = {}) { await TestBed.configureTestingModule({ imports: [TaskExecutionInputsPanelComponent] }).compileComponents(); const fixture = TestBed.createComponent(TaskExecutionInputsPanelComponent); fixture.componentRef.setInput('editableInputs', inputs); fixture.componentRef.setInput('pendingKeys', options.pendingKeys ?? []); fixture.componentRef.setInput('savingInputs', options.saving ?? {}); fixture.componentRef.setInput('readOnly', options.readOnly ?? false); fixture.detectChanges(); await fixture.whenStable(); fixture.detectChanges(); return fixture; } describe('TaskExecutionInputsPanelComponent', () => { afterEach(() => TestBed.resetTestingModule()); it('counts every input, node ones included, since all of them are required', async () => { // A step without its manual input never reaches READY, so a node input blocks the start just // as a global one does and must be part of the tally. const fixture = await build([ makeInput({ key: 'g:a', inputName: 'a', provided: true }), makeInput({ key: 'g:b', inputName: 'b' }), makeInput({ key: 'n:c', scope: 'node', inputName: 'c', title: 'Reviewer' }) ]); expect(fixture.componentInstance.providedCount()).toBe(1); expect(fixture.nativeElement.textContent).toContain('1 of 3 provided'); }); it('marks exactly the inputs with nothing stored', async () => { const provided = makeInput({ key: 'g:a', inputName: 'a', provided: true }); const missing = makeInput({ key: 'n:b', scope: 'node', inputName: 'b', title: 'Reviewer' }); const fixture = await build([provided, missing]); expect(fixture.componentInstance.isMissing(provided)).toBe(false); expect(fixture.componentInstance.isMissing(missing)).toBe(true); }); it('names the two groups after the domain: global and node inputs', async () => { const fixture = await build([ makeInput({ key: 'g:a', inputName: 'a' }), makeInput({ key: 'n:b', scope: 'node', inputName: 'b', title: 'Reviewer' }) ]); const text = fixture.nativeElement.textContent; expect(text).toContain('Global inputs'); expect(text).toContain('Node inputs'); }); it('offers a single save for every pending edit', async () => { const fixture = await build( [makeInput({ key: 'g:a', inputName: 'a' }), makeInput({ key: 'g:b', inputName: 'b' })], { pendingKeys: ['g:a', 'g:b'] } ); expect(fixture.componentInstance.pendingCount()).toBe(2); expect(fixture.componentInstance.canSubmitAll()).toBe(true); expect(fixture.nativeElement.textContent).toContain('2 unsaved changes'); const submitted = vi.fn(); fixture.componentInstance.submitAllInputs.subscribe(submitted); fixture.componentInstance.submitAll(); expect(submitted).toHaveBeenCalledTimes(1); }); it('says everything is saved, and refuses to save, with nothing pending', async () => { const fixture = await build([makeInput()], {}); expect(fixture.componentInstance.canSubmitAll()).toBe(false); expect(fixture.nativeElement.textContent).toContain('All changes saved'); const submitted = vi.fn(); fixture.componentInstance.submitAllInputs.subscribe(submitted); fixture.componentInstance.submitAll(); expect(submitted).not.toHaveBeenCalled(); }); it('does not let a second save start while one is in flight', async () => { const fixture = await build([makeInput({ key: 'g:a', inputName: 'a' })], { pendingKeys: ['g:a'], saving: { 'g:a': true } }); expect(fixture.componentInstance.anySaving()).toBe(true); expect(fixture.componentInstance.canSubmitAll()).toBe(false); }); it('hides the save bar when the panel is read-only', async () => { const fixture = await build([makeInput()], { readOnly: true, pendingKeys: ['global:role'] }); expect(fixture.nativeElement.querySelector('.inputs-panel-savebar')).toBeNull(); expect(fixture.componentInstance.canSubmitAll()).toBe(false); }); it('renders one row per item of a multi-value input, plus a way to add one', async () => { const fixture = await build([makeInput({ multiple: true, type: 'TEXT', value: ['one', 'two'] })]); expect(fixture.nativeElement.querySelectorAll('.inputs-panel-row').length).toBe(2); expect(fixture.nativeElement.querySelector('.inputs-panel-add')).not.toBeNull(); // The type label says it is a list, which is what the editor is offering. expect(fixture.nativeElement.textContent).toContain('TEXT[]'); }); });