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:
Charlie Kolb 2026-08-17 10:57:15 +00:00 committed by GitHub
parent 674c9e67b4
commit 02bc2bf533
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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();