mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
test(editor): Stop AgentBuilderView tests from leaking async work across tests (#36412)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
674c9e67b4
commit
02bc2bf533
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable import-x/no-extraneous-dependencies, @typescript-eslint/no-unsafe-assignment -- test-only patterns: @vue/test-utils is a transitive devDep and private-state reads */
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { mount, flushPromises } from '@vue/test-utils';
|
||||
import { nextTick, ref, computed, reactive } from 'vue';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
|
|
@ -393,10 +393,25 @@ async function renderView({
|
|||
stubs: commonStubs,
|
||||
},
|
||||
});
|
||||
mountedWrappers.push(wrapper);
|
||||
if (waitForAsyncSetup) await flushPromises();
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
const mountedWrappers: Array<ReturnType<typeof mount>> = [];
|
||||
|
||||
// Unmount every view mounted by the test and drain its async tail. Without
|
||||
// this, a still-mounted view's pending debounce timers and in-flight save
|
||||
// chains keep running into later tests, where they consume shared mock
|
||||
// `*Once` queues and emit bus events against the next test's component.
|
||||
// A second unmount after a test's own `wrapper.unmount()` is a no-op.
|
||||
afterEach(async () => {
|
||||
for (const wrapper of mountedWrappers.splice(0)) {
|
||||
wrapper.unmount();
|
||||
}
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
async function startArtifactPreviewSend(
|
||||
wrapper: Awaited<ReturnType<typeof renderView>>,
|
||||
sessionId: string,
|
||||
|
|
@ -1584,11 +1599,17 @@ describe('AgentBuilderView — preview routing', { timeout: 60_000 }, () => {
|
|||
.spyOn(useMCPStore(), 'toggleAgentMcpAccess')
|
||||
.mockReturnValueOnce(mcpSave.promise);
|
||||
|
||||
// A failed initialize() only shows a toast, then renders the editor with a
|
||||
// null agent — which silently drops the toggle below and surfaces three
|
||||
// asserts later as an autosave spy with zero calls. Fail here instead,
|
||||
// with the swallowed error visible in the spy's recorded calls.
|
||||
expect(showErrorMock).not.toHaveBeenCalled();
|
||||
const editorColumn = wrapper.findComponent({ name: 'AgentBuilderEditorColumn' });
|
||||
expect(editorColumn.props('agent')).toBeTruthy();
|
||||
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
wrapper
|
||||
.findComponent({ name: 'AgentBuilderEditorColumn' })
|
||||
.vm.$emit('toggle-mcp-access', true);
|
||||
editorColumn.vm.$emit('toggle-mcp-access', true);
|
||||
await nextTick();
|
||||
await vi.advanceTimersByTimeAsync(500);
|
||||
expect(toggleAgentMcpAccess).toHaveBeenCalledExactlyOnceWith('a2', true);
|
||||
|
|
@ -2071,11 +2092,15 @@ describe('AgentBuilderView — three-column shell', () => {
|
|||
unchangedIds: [],
|
||||
});
|
||||
|
||||
// See the sibling MCP test above: a failed initialize() drops the toggle
|
||||
// silently, so guard the mount before emitting.
|
||||
expect(showErrorMock).not.toHaveBeenCalled();
|
||||
const editorColumn = wrapper.findComponent({ name: 'AgentBuilderEditorColumn' });
|
||||
expect(editorColumn.props('agent')).toBeTruthy();
|
||||
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
wrapper
|
||||
.findComponent({ name: 'AgentBuilderEditorColumn' })
|
||||
.vm.$emit('toggle-mcp-access', true);
|
||||
editorColumn.vm.$emit('toggle-mcp-access', true);
|
||||
await nextTick();
|
||||
|
||||
await wrapper.setProps({ artifactAgentId: 'a2' });
|
||||
|
|
@ -2942,17 +2967,11 @@ describe('AgentBuilderView — three-column shell', () => {
|
|||
});
|
||||
|
||||
it('shows the loading spinner while initialize() is in flight and hides it after', async () => {
|
||||
const { default: AgentBuilderView } = await import('../views/AgentBuilderView.vue');
|
||||
const pinia = createPinia();
|
||||
setActivePinia(pinia);
|
||||
|
||||
// A promise that we control — lets us capture the intermediate loading state.
|
||||
let resolveAgent!: (v: unknown) => void;
|
||||
getAgentMock.mockReturnValueOnce(new Promise((r) => (resolveAgent = r)));
|
||||
|
||||
const wrapper = mount(AgentBuilderView, {
|
||||
global: { plugins: [pinia], stubs: commonStubs },
|
||||
});
|
||||
const wrapper = await renderView({ waitForAsyncSetup: false });
|
||||
|
||||
// initialize() hasn't resolved yet → spinner visible, content hidden.
|
||||
await nextTick();
|
||||
|
|
@ -2969,15 +2988,9 @@ describe('AgentBuilderView — three-column shell', () => {
|
|||
});
|
||||
|
||||
it('clears the loading spinner and shows an error when initialize() throws (finally path)', async () => {
|
||||
const { default: AgentBuilderView } = await import('../views/AgentBuilderView.vue');
|
||||
const pinia = createPinia();
|
||||
setActivePinia(pinia);
|
||||
|
||||
getAgentMock.mockRejectedValueOnce(new Error('network error'));
|
||||
|
||||
const wrapper = mount(AgentBuilderView, {
|
||||
global: { plugins: [pinia], stubs: commonStubs },
|
||||
});
|
||||
const wrapper = await renderView({ waitForAsyncSetup: false });
|
||||
|
||||
await flushPromises();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user