humainflow-web/src/app/shared/task-execution-inputs-panel/task-execution-inputs-panel...

128 lines
5.0 KiB
TypeScript

import { TestBed } from '@angular/core/testing';
import { vi } from 'vitest';
import { EditableExecutionInput, TaskExecutionInputsPanelComponent } from './task-execution-inputs-panel';
function makeInput(overrides: Partial<EditableExecutionInput> = {}): 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<string, boolean>;
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[]');
});
});