Skip to content

Commit 961b363

Browse files
committed
🤖 tests: reinstall DnD module mocks before each suite
Re-register the AppLoader auth and ProjectSidebar drag-and-drop module mocks in beforeEach so `mock.restore()` cannot leave later tests resolving the real react-dnd stack before happy-dom exists. --- _Generated with `mux` • Model: `openai:gpt-5.4` • Thinking: `xhigh` • Cost: `$447.91`_ <!-- mux-attribution: model=openai:gpt-5.4 thinking=xhigh costs=447.91 -->
1 parent 4bf3810 commit 961b363

2 files changed

Lines changed: 95 additions & 89 deletions

File tree

src/browser/components/AppLoader/AppLoader.auth.test.tsx

Lines changed: 82 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -13,97 +13,100 @@ let apiError: string | null = "Authentication required";
1313

1414
const passthroughRef = <T,>(value: T): T => value;
1515

16-
void mock.module("react-dnd", () => ({
17-
DndProvider: (props: { children: React.ReactNode }) => props.children,
18-
useDrag: () => [{ isDragging: false }, passthroughRef, () => undefined] as const,
19-
useDrop: () => [{ isOver: false }, passthroughRef] as const,
20-
useDragLayer: () => ({ isDragging: false, item: null, currentOffset: null }),
21-
}));
22-
23-
void mock.module("react-dnd-html5-backend", () => ({
24-
HTML5Backend: {},
25-
getEmptyImage: () => null,
26-
}));
27-
28-
// AppLoader imports App, which pulls in Lottie-based components. In happy-dom,
29-
// lottie-web's canvas bootstrap can throw during module evaluation.
30-
void mock.module("lottie-react", () => ({
31-
__esModule: true,
32-
default: () => <div data-testid="LottieMock" />,
33-
}));
34-
35-
void mock.module("@/browser/contexts/API", () => ({
36-
APIProvider: (props: { children: React.ReactNode }) => props.children,
37-
useAPI: () => {
38-
if (apiStatus === "auth_required") {
39-
return {
40-
api: null,
41-
status: "auth_required" as const,
42-
error: apiError,
43-
authenticate: () => undefined,
44-
retry: () => undefined,
45-
};
46-
}
16+
function installAppLoaderModuleMocks() {
17+
void mock.module("react-dnd", () => ({
18+
DndProvider: (props: { children: React.ReactNode }) => props.children,
19+
useDrag: () => [{ isDragging: false }, passthroughRef, () => undefined] as const,
20+
useDrop: () => [{ isOver: false }, passthroughRef] as const,
21+
useDragLayer: () => ({ isDragging: false, item: null, currentOffset: null }),
22+
}));
23+
24+
void mock.module("react-dnd-html5-backend", () => ({
25+
HTML5Backend: {},
26+
getEmptyImage: () => null,
27+
}));
28+
29+
// AppLoader imports App, which pulls in Lottie-based components. In happy-dom,
30+
// lottie-web's canvas bootstrap can throw during module evaluation.
31+
void mock.module("lottie-react", () => ({
32+
__esModule: true,
33+
default: () => <div data-testid="LottieMock" />,
34+
}));
35+
36+
void mock.module("@/browser/contexts/API", () => ({
37+
APIProvider: (props: { children: React.ReactNode }) => props.children,
38+
useAPI: () => {
39+
if (apiStatus === "auth_required") {
40+
return {
41+
api: null,
42+
status: "auth_required" as const,
43+
error: apiError,
44+
authenticate: () => undefined,
45+
retry: () => undefined,
46+
};
47+
}
48+
49+
if (apiStatus === "error") {
50+
return {
51+
api: null,
52+
status: "error" as const,
53+
error: apiError ?? "Connection error",
54+
authenticate: () => undefined,
55+
retry: () => undefined,
56+
};
57+
}
4758

48-
if (apiStatus === "error") {
4959
return {
5060
api: null,
51-
status: "error" as const,
52-
error: apiError ?? "Connection error",
61+
status: "connecting" as const,
62+
error: null,
5363
authenticate: () => undefined,
5464
retry: () => undefined,
5565
};
56-
}
57-
58-
return {
59-
api: null,
60-
status: "connecting" as const,
61-
error: null,
62-
authenticate: () => undefined,
63-
retry: () => undefined,
64-
};
65-
},
66-
}));
67-
68-
void mock.module("../../App", () => ({
69-
__esModule: true,
70-
// App imports the full sidebar tree (including react-dnd) even though these auth-path tests only
71-
// need AppLoader's pre-App branching. Keep the unit focused so cross-file DOM teardown cannot
72-
// trip react-dnd's MutationObserver bootstrap between test files.
73-
default: () => <div data-testid="AppMock" />,
74-
}));
75-
76-
void mock.module("@/browser/components/LoadingScreen/LoadingScreen", () => ({
77-
LoadingScreen: () => {
78-
const { theme } = useTheme();
79-
return <div data-testid="LoadingScreenMock">{theme}</div>;
80-
},
81-
}));
82-
83-
void mock.module("@/browser/components/StartupConnectionError/StartupConnectionError", () => ({
84-
StartupConnectionError: (props: { error: string }) => (
85-
<div data-testid="StartupConnectionErrorMock">{props.error}</div>
86-
),
87-
}));
88-
89-
void mock.module("@/browser/components/AuthTokenModal/AuthTokenModal", () => ({
90-
// Note: Module mocks leak between bun test files.
91-
// Export all commonly-used symbols to avoid cross-test import errors.
92-
AuthTokenModal: (props: { error?: string | null }) => (
93-
<div data-testid="AuthTokenModalMock">{props.error ?? "no-error"}</div>
94-
),
95-
getStoredAuthToken: () => null,
96-
// eslint-disable-next-line @typescript-eslint/no-empty-function
97-
setStoredAuthToken: () => {},
98-
// eslint-disable-next-line @typescript-eslint/no-empty-function
99-
clearStoredAuthToken: () => {},
100-
}));
66+
},
67+
}));
68+
69+
void mock.module("../../App", () => ({
70+
__esModule: true,
71+
// App imports the full sidebar tree (including react-dnd) even though these auth-path tests
72+
// only need AppLoader's pre-App branching. Keep the unit focused so cross-file DOM teardown
73+
// cannot trip react-dnd's MutationObserver bootstrap between test files.
74+
default: () => <div data-testid="AppMock" />,
75+
}));
76+
77+
void mock.module("@/browser/components/LoadingScreen/LoadingScreen", () => ({
78+
LoadingScreen: () => {
79+
const { theme } = useTheme();
80+
return <div data-testid="LoadingScreenMock">{theme}</div>;
81+
},
82+
}));
83+
84+
void mock.module("@/browser/components/StartupConnectionError/StartupConnectionError", () => ({
85+
StartupConnectionError: (props: { error: string }) => (
86+
<div data-testid="StartupConnectionErrorMock">{props.error}</div>
87+
),
88+
}));
89+
90+
void mock.module("@/browser/components/AuthTokenModal/AuthTokenModal", () => ({
91+
// Note: Module mocks leak between bun test files.
92+
// Export all commonly-used symbols to avoid cross-test import errors.
93+
AuthTokenModal: (props: { error?: string | null }) => (
94+
<div data-testid="AuthTokenModalMock">{props.error ?? "no-error"}</div>
95+
),
96+
getStoredAuthToken: () => null,
97+
// eslint-disable-next-line @typescript-eslint/no-empty-function
98+
setStoredAuthToken: () => {},
99+
// eslint-disable-next-line @typescript-eslint/no-empty-function
100+
clearStoredAuthToken: () => {},
101+
}));
102+
}
101103

102104
let AppLoader: (typeof import("../AppLoader/AppLoader"))["AppLoader"];
103105

104106
describe("AppLoader", () => {
105107
beforeEach(async () => {
106108
cleanupDom = installDom();
109+
installAppLoaderModuleMocks();
107110
({ AppLoader } = await import("../AppLoader/AppLoader"));
108111
});
109112

src/browser/components/ProjectSidebar/ProjectSidebar.test.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,19 @@ const passthroughRef = <T,>(value: T): T => value;
4848

4949
let ProjectSidebar: (typeof import("./ProjectSidebar"))["default"];
5050

51-
void mock.module("react-dnd", () => ({
52-
DndProvider: TestWrapper,
53-
useDrag: () => [{ isDragging: false }, passthroughRef, () => undefined] as const,
54-
useDrop: () => [{ isOver: false }, passthroughRef] as const,
55-
useDragLayer: () => ({ isDragging: false, item: null, currentOffset: null }),
56-
}));
51+
function installProjectSidebarModuleMocks() {
52+
void mock.module("react-dnd", () => ({
53+
DndProvider: TestWrapper,
54+
useDrag: () => [{ isDragging: false }, passthroughRef, () => undefined] as const,
55+
useDrop: () => [{ isOver: false }, passthroughRef] as const,
56+
useDragLayer: () => ({ isDragging: false, item: null, currentOffset: null }),
57+
}));
5758

58-
void mock.module("react-dnd-html5-backend", () => ({
59-
HTML5Backend: {},
60-
getEmptyImage: () => null,
61-
}));
59+
void mock.module("react-dnd-html5-backend", () => ({
60+
HTML5Backend: {},
61+
getEmptyImage: () => null,
62+
}));
63+
}
6264

6365
function resolveVoidResult() {
6466
return Promise.resolve({ success: true as const, data: undefined });
@@ -305,6 +307,7 @@ describe("ProjectSidebar multi-project completed-subagent toggles", () => {
305307
EXPANDED_PROJECTS_KEY,
306308
JSON.stringify([MULTI_PROJECT_SIDEBAR_SECTION_ID])
307309
);
310+
installProjectSidebarModuleMocks();
308311
installProjectSidebarTestDoubles();
309312
({ default: ProjectSidebar } = await import("./ProjectSidebar"));
310313
});

0 commit comments

Comments
 (0)