humainflow-web/src/app/layouts/flow-editor/flow-editor.spec.ts

164 lines
5.1 KiB
TypeScript

// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii <lucio.lelii@isti.cnr.it> - ISTI-CNR
// SPDX-License-Identifier: AGPL-3.0-or-later
// Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AssistantSessionStore, AssistantSessionSnapshot } from '@stores/assistant-session-store';
import { EditorStateHolder } from '@stores/flow-editor';
import { FlowEditor } from './flow-editor';
describe('FlowEditor', () => {
let component: FlowEditor;
let fixture: ComponentFixture<FlowEditor>;
let editorState: EditorStateHolder;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FlowEditor]
})
.compileComponents();
fixture = TestBed.createComponent(FlowEditor);
component = fixture.componentInstance;
editorState = TestBed.inject(EditorStateHolder);
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('keeps the refine assistant collapsed when a flow opens without subflows', async () => {
editorState.currentFlow.set({
id: 'flow-1',
name: 'Test Flow',
visibility: 'PRIVATE',
author: 'tester',
createdAt: new Date(),
updatedAt: new Date(),
status: 'DRAFT',
data: {
blocks: [],
containers: [],
connections: [],
dependencies: []
},
validationErrors: []
});
fixture.detectChanges();
await fixture.whenStable();
expect(component.assistantOpen()).toBe(false);
});
it('collapses the refine assistant again when switching to another flow', async () => {
editorState.currentFlow.set({
id: 'flow-1',
name: 'First Flow',
visibility: 'PRIVATE',
author: 'tester',
createdAt: new Date(),
updatedAt: new Date(),
status: 'DRAFT',
data: {
blocks: [],
containers: [],
connections: [],
dependencies: []
},
validationErrors: []
});
fixture.detectChanges();
await fixture.whenStable();
component.assistantOpen.set(true);
editorState.currentFlow.set({
id: 'flow-2',
name: 'Second Flow',
visibility: 'PRIVATE',
author: 'tester',
createdAt: new Date(),
updatedAt: new Date(),
status: 'DRAFT',
data: {
blocks: [],
containers: [],
connections: [],
dependencies: []
},
validationErrors: []
});
fixture.detectChanges();
await fixture.whenStable();
expect(component.assistantOpen()).toBe(false);
});
describe('the AI flow creation button', () => {
/** Enough of a snapshot to be stored; its contents do not matter to the restore decision. */
function storeCreateModalSnapshot() {
const store = TestBed.inject(AssistantSessionStore);
store.setSnapshot(AssistantSessionStore.CREATE_MODAL_FLOW_KEY, {
sessionId: null,
selectedModel: '',
useDefaultConfiguration: true,
selectedProvider: '',
phaseModels: {},
advancedModelsOpen: false,
prompt: '',
modelPickerOpen: false,
quickPromptsOpen: false,
localMessages: [],
currentCall: null,
sessionState: null,
assistantErrorMessage: null,
lastFailedPrompt: null,
lastSubmittedPrompt: ''
} as AssistantSessionSnapshot);
return store;
}
/**
* A component whose computeds have not run yet. `showCreateAssistantFloatingButton` starts with
* `assistantEnabled`, which the test build ships as false: evaluating it once in that state
* short-circuits before reading a single signal, so the computed registers no dependencies and
* stays false forever. Harmless in a real build, where the flag is a build-time constant.
*/
function freshEditor() {
const created = TestBed.createComponent(FlowEditor);
(created.componentInstance as any).assistantEnabled = true;
return created.componentInstance;
}
it('stops coming back once the modal has been closed', async () => {
// Closing used to leave the snapshot behind unless it had just cancelled a running call, so
// the floating button reappeared on every later load, advertising a creation that was over.
const store = storeCreateModalSnapshot();
const editor = freshEditor();
editor.aiCreationRequested.set(true);
editor.aiCreationMinimized.set(true);
expect(editor.showCreateAssistantFloatingButton()).toBe(true);
await editor.closeCreateWithAi();
expect(store.hasSnapshot(AssistantSessionStore.CREATE_MODAL_FLOW_KEY)).toBe(false);
expect(editor.showCreateAssistantFloatingButton()).toBe(false);
});
it('still comes back for a creation that was only minimized', () => {
// The restore exists for a reload during a running generation; closing is what must clear it.
storeCreateModalSnapshot();
const editor = freshEditor();
(editor as any).restoreMinimizedCreateAssistantIfAvailable();
expect(editor.showCreateAssistantFloatingButton()).toBe(true);
});
});
});