164 lines
5.1 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|