qwen-code/packages/web-shell/client/index.test.tsx
ytahdn 1467ed3100
fix(web-shell): defer session creation until first prompt (#6066)
* fix(web-shell): defer session creation until first prompt

* fix(web-shell): stabilize deferred session attach

* docs(web-shell): document optional session change callback

* test(web-shell): cover deferred session setup failures

* fix(webui): preserve concurrent session load on clear

* fix(web-shell): keep session prep helper in utils

* fix(web-shell): harden deferred session lifecycle

* fix(web-shell): guard deferred session races

* fix(web-shell): simplify controlled session selection

* fix(web-shell): tighten empty session edge cases

* fix(web-shell): tighten deferred session lifecycle

* test(webui): align session action mocks with daemon types

* fix(web-shell): notify cleared session ids

* fix(webui): guard session cleanup races

* fix(web-shell): preserve blocked local commands

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-07-01 14:04:37 +00:00

105 lines
3.8 KiB
TypeScript

// @vitest-environment jsdom
import { afterEach, describe, expect, it, vi } from 'vitest';
import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
// Drive a render throw from inside DaemonWorkspaceProvider so we can prove the
// top-level boundary sits *outside* the daemon providers (a boundary nested
// under them couldn't catch their own throw).
let workspaceShouldThrow = false;
const sessionProviderProps: Array<Record<string, unknown>> = [];
vi.mock('@qwen-code/webui/daemon-react-sdk', async () => {
const React = await import('react');
return {
DaemonWorkspaceProvider: ({ children }: { children: React.ReactNode }) => {
if (workspaceShouldThrow) throw new Error('provider boom');
return React.createElement(React.Fragment, null, children);
},
DaemonSessionProvider: ({
children,
...props
}: {
children: React.ReactNode;
}) => {
sessionProviderProps.push(props);
return React.createElement(React.Fragment, null, children);
},
};
});
vi.mock('./App', async () => {
const React = await import('react');
return {
App: () => React.createElement('div', { 'data-testid': 'app-ok' }, 'app'),
};
});
// Bare './index' resolves to the sibling index.ts barrel, which doesn't export
// WebShellWithProviders (see the dual-entry note in the PR). A variable
// specifier loads index.tsx at runtime without tripping tsc's ts-extension rule.
const indexEntry = './index.tsx';
const { WebShellWithProviders } = await import(indexEntry);
(
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
const mounted: Array<{ root: Root; container: HTMLElement }> = [];
function render(node: React.ReactNode): HTMLElement {
const container = document.createElement('div');
document.body.appendChild(container);
const root = createRoot(container);
act(() => root.render(node));
mounted.push({ root, container });
return container;
}
afterEach(() => {
for (const { root, container } of mounted.splice(0)) {
act(() => root.unmount());
container.remove();
}
workspaceShouldThrow = false;
sessionProviderProps.length = 0;
vi.restoreAllMocks();
});
describe('WebShellWithProviders top-level boundary', () => {
it('renders normally when the providers are healthy', () => {
const container = render(<WebShellWithProviders />);
expect(container.querySelector('[data-testid="app-ok"]')).not.toBeNull();
expect(container.querySelector('[role="alert"]')).toBeNull();
});
it('starts on an empty session by default', () => {
render(<WebShellWithProviders />);
expect(sessionProviderProps[0]).toMatchObject({
sessionId: undefined,
});
expect(sessionProviderProps[0]).not.toHaveProperty('deferSessionCreation');
});
it('passes controlled sessionId to the daemon session provider', () => {
render(<WebShellWithProviders sessionId="session-2" />);
expect(sessionProviderProps[0]).toMatchObject({
sessionId: 'session-2',
});
});
it('passes explicit undefined sessionId to the daemon session provider', () => {
render(<WebShellWithProviders sessionId={undefined} />);
expect(sessionProviderProps[0]).toHaveProperty('sessionId', undefined);
});
it('catches a daemon-provider render crash instead of white-screening', () => {
vi.spyOn(console, 'error').mockImplementation(() => {});
workspaceShouldThrow = true;
const container = render(<WebShellWithProviders />);
// The boundary is outside the providers, so the provider throw degrades to
// the recoverable fallback rather than unmounting the whole root.
expect(container.querySelector('[role="alert"]')?.textContent).toContain(
'Something went wrong',
);
expect(container.querySelector('[data-testid="app-ok"]')).toBeNull();
});
});