Skip to content

Commit e5d720d

Browse files
chiciometa-codesync[bot]
authored andcommitted
Added missing tests for Standalone fusebox shell (#55460)
Summary: This PR adds the missing test coverage for the standalone Fusebox debugger shell in `StandaloneFuseboxShell-test`. First, I added some missing expectation for the success case (`enableStandaloneFuseboxShell=true` and no errors) already present: - the assertion that `unstable_prepareFuseboxShell` is called just one time during middleware initialization - the assertion that `launchDebuggerAppWindow` is never called when the standalone shell is available Then, I added the missing test for the case when `unstable_prepareFuseboxShell` returns one of the error code like eg. `platform_not_supported` (as reported also in the TODO that I removed). In this way now the test is verifying the full behaviour for the new standalone mode for debugger/dev tools. ## Changelog: [GENERAL] [CHANGED] - Added missing tests for Standalone fusebox shell Pull Request resolved: #55460 Test Plan: - Ran `StandaloneFuseboxShell-test` and verify all tests cases passed - Ran the full React Native test suite to ensure all tests pass. - Verified test correctness by intentionally breaking production code: - removing the calls to the `DefaultBrowserLauncher` in `openDebuggerMiddleware` to verify that the tests were correctly failing Reviewed By: Abbondanzo Differential Revision: D93157076 Pulled By: huntie fbshipit-source-id: 51c12ad67dd929035f779b53add9163a6619cf41
1 parent 7e8a560 commit e5d720d

1 file changed

Lines changed: 181 additions & 78 deletions

File tree

packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js

Lines changed: 181 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
*/
1010

1111
import type {JsonPagesListResponse} from '../inspector-proxy/types';
12-
import type {DevToolLauncher} from '../types/DevToolLauncher';
12+
import type {
13+
DebuggerShellPreparationResult,
14+
DevToolLauncher,
15+
} from '../types/DevToolLauncher';
1316

1417
import {fetchJson, requestLocal} from './FetchUtils';
1518
import {createDeviceMock} from './InspectorDeviceUtils';
@@ -21,112 +24,212 @@ const PAGES_POLLING_DELAY = 2100;
2124

2225
jest.useFakeTimers();
2326

24-
describe('enableStandaloneFuseboxShell experiment', () => {
27+
async function setupDevice(
28+
serverRef: {+serverBaseWsUrl: string, ...},
29+
signal: AbortSignal,
30+
) {
31+
const device = await createDeviceMock(
32+
`${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`,
33+
signal,
34+
);
35+
device.getPages.mockImplementation(() => [
36+
{
37+
app: 'bar-app',
38+
id: 'page1',
39+
title: 'bar-title',
40+
vm: 'bar-vm',
41+
capabilities: {
42+
nativePageReloads: true,
43+
},
44+
},
45+
]);
46+
jest.advanceTimersByTime(PAGES_POLLING_DELAY);
47+
48+
return device;
49+
}
50+
51+
function setupToolLauncherWithFuseboxShell(
52+
prepareDebuggerShell: () => Promise<DebuggerShellPreparationResult>,
53+
) {
2554
const ToolLauncherWithFuseboxShell: DevToolLauncher = {
2655
launchDebuggerAppWindow: async (url: string) => {},
2756
launchDebuggerShell: () => {
2857
throw new Error('Not implemented');
2958
},
30-
prepareDebuggerShell: async () => {
31-
return {code: 'not_implemented'};
32-
},
59+
prepareDebuggerShell,
3360
};
34-
const serverRef = withServerForEachTest({
35-
logger: undefined,
36-
unstable_toolLauncher: ToolLauncherWithFuseboxShell,
37-
unstable_experiments: {
38-
enableStandaloneFuseboxShell: true,
39-
},
40-
});
61+
62+
const prepareDebuggerShellSpy = jest.spyOn(
63+
ToolLauncherWithFuseboxShell,
64+
'prepareDebuggerShell',
65+
);
66+
67+
return {
68+
ToolLauncherWithFuseboxShell,
69+
prepareDebuggerShellSpy,
70+
};
71+
}
72+
73+
describe('enableStandaloneFuseboxShell experiment', () => {
4174
const autoCleanup = withAbortSignalForEachTest();
75+
4276
afterEach(() => {
4377
jest.clearAllMocks();
4478
});
4579

4680
describe('/open-debugger endpoint', () => {
47-
test('launches the shell with a frontend URL and stable window key', async () => {
48-
// Connect a device to use when opening the debugger
49-
const device = await createDeviceMock(
50-
`${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`,
51-
autoCleanup.signal,
52-
);
53-
device.getPages.mockImplementation(() => [
54-
{
55-
app: 'bar-app',
56-
id: 'page1',
57-
title: 'bar-title',
58-
vm: 'bar-vm',
59-
capabilities: {
60-
// Ensure the device target can be found when launching the debugger
61-
nativePageReloads: true,
62-
},
63-
},
64-
]);
65-
jest.advanceTimersByTime(PAGES_POLLING_DELAY);
66-
67-
const launchDebuggerAppWindowSpy = jest
68-
.spyOn(ToolLauncherWithFuseboxShell, 'launchDebuggerAppWindow')
69-
.mockResolvedValue();
70-
const showFuseboxShellSpy = jest
71-
.spyOn(ToolLauncherWithFuseboxShell, 'launchDebuggerShell')
72-
.mockResolvedValue();
73-
74-
try {
75-
// Fetch the target information for the device
76-
const pageListResponse = await fetchJson<JsonPagesListResponse>(
77-
`${serverRef.serverBaseUrl}/json`,
78-
);
79-
// Select the first target from the page list response
80-
expect(pageListResponse.length).toBeGreaterThanOrEqual(1);
81-
const firstPage = pageListResponse[0];
82-
83-
// Build the URL for the debugger
84-
const openUrl = new URL('/open-debugger', serverRef.serverBaseUrl);
85-
openUrl.searchParams.set('launchId', 'launch1');
86-
openUrl.searchParams.set(
87-
'device',
88-
firstPage.reactNative.logicalDeviceId,
81+
describe('success', () => {
82+
const {ToolLauncherWithFuseboxShell, prepareDebuggerShellSpy} =
83+
setupToolLauncherWithFuseboxShell(() =>
84+
Promise.resolve({code: 'success'}),
8985
);
90-
openUrl.searchParams.set('target', firstPage.id);
91-
// Request to open the debugger for the first device
92-
{
86+
const server = withServerForEachTest({
87+
logger: undefined,
88+
unstable_toolLauncher: ToolLauncherWithFuseboxShell,
89+
unstable_experiments: {
90+
enableStandaloneFuseboxShell: true,
91+
},
92+
});
93+
94+
test('launches the shell with a frontend URL and stable window key', async () => {
95+
// Connect a device to use when opening the debugger
96+
const device = await setupDevice(server, autoCleanup.signal);
97+
98+
const launchDebuggerAppWindowSpy = jest
99+
.spyOn(ToolLauncherWithFuseboxShell, 'launchDebuggerAppWindow')
100+
.mockResolvedValue();
101+
const showFuseboxShellSpy = jest
102+
.spyOn(ToolLauncherWithFuseboxShell, 'launchDebuggerShell')
103+
.mockResolvedValue();
104+
105+
try {
106+
// Fetch the target information for the device
107+
const pageListResponse = await fetchJson<JsonPagesListResponse>(
108+
`${server.serverBaseUrl}/json`,
109+
);
110+
// Select the first target from the page list response
111+
expect(pageListResponse.length).toBeGreaterThanOrEqual(1);
112+
const firstPage = pageListResponse[0];
113+
114+
// Build the URL for the debugger
115+
const openUrl = new URL('/open-debugger', server.serverBaseUrl);
116+
openUrl.searchParams.set('launchId', 'launch1');
117+
openUrl.searchParams.set(
118+
'device',
119+
firstPage.reactNative.logicalDeviceId,
120+
);
121+
openUrl.searchParams.set('target', firstPage.id);
122+
// Request to open the debugger for the first device
93123
const response = await requestLocal(openUrl.toString(), {
94124
method: 'POST',
95125
});
126+
96127
// Ensure the request was handled properly
97128
expect(response.statusCode).toBe(200);
129+
130+
// Debugger was launched but will fail to prepare standalone shell
131+
expect(prepareDebuggerShellSpy).toHaveBeenCalled();
132+
133+
// Ensure the debugger was launched using the standalone shell API
134+
expect(showFuseboxShellSpy).toHaveBeenCalledWith(
135+
expect.any(String),
136+
expect.any(String),
137+
);
138+
139+
// No call to the regular browser launcher since standalone shell should be used
140+
expect(launchDebuggerAppWindowSpy).not.toHaveBeenCalled();
141+
142+
const firstWindowKey = showFuseboxShellSpy.mock.calls[0][1];
143+
144+
showFuseboxShellSpy.mockClear();
145+
openUrl.searchParams.set('launchId', 'launch2');
146+
147+
const anotherResponse = await requestLocal(openUrl.toString(), {
148+
method: 'POST',
149+
});
150+
151+
// Ensure the request was handled properly
152+
expect(anotherResponse.statusCode).toBe(200);
153+
154+
// Ensure the debugger was launched using the standalone shell API and the same window key
155+
expect(showFuseboxShellSpy).toHaveBeenCalledWith(
156+
expect.any(String),
157+
firstWindowKey,
158+
);
159+
160+
// Ensure the debugger preparation function was called, just one time, during middleware initialization
161+
expect(showFuseboxShellSpy).toHaveBeenCalledTimes(1);
162+
163+
// No fallback needed
164+
expect(launchDebuggerAppWindowSpy).not.toHaveBeenCalled();
165+
} finally {
166+
device.close();
98167
}
99-
openUrl.searchParams.set('launchId', 'launch1');
168+
});
169+
});
100170

101-
// Ensure the debugger was launched using the standalone shell API
102-
expect(showFuseboxShellSpy).toHaveBeenCalledWith(
103-
expect.any(String),
104-
expect.any(String),
171+
describe('prepareDebuggerShell failures', () => {
172+
const {ToolLauncherWithFuseboxShell, prepareDebuggerShellSpy} =
173+
setupToolLauncherWithFuseboxShell(() =>
174+
Promise.resolve({code: 'platform_not_supported'}),
105175
);
106-
const firstWindowKey = showFuseboxShellSpy.mock.calls[0][1];
176+
const server = withServerForEachTest({
177+
logger: undefined,
178+
unstable_toolLauncher: ToolLauncherWithFuseboxShell,
179+
unstable_experiments: {
180+
enableStandaloneFuseboxShell: true,
181+
},
182+
});
183+
184+
test('falls back to browser window when preparation fails', async () => {
185+
// Connect a device to use when opening the debugger
186+
const device = await setupDevice(server, autoCleanup.signal);
187+
188+
const launchDebuggerAppWindowSpy = jest
189+
.spyOn(ToolLauncherWithFuseboxShell, 'launchDebuggerAppWindow')
190+
.mockResolvedValue();
191+
const showFuseboxShellSpy = jest
192+
.spyOn(ToolLauncherWithFuseboxShell, 'launchDebuggerShell')
193+
.mockResolvedValue();
107194

108-
showFuseboxShellSpy.mockClear();
109-
openUrl.searchParams.set('launchId', 'launch2');
195+
try {
196+
// Fetch the target information for the device
197+
const pageListResponse = await fetchJson<JsonPagesListResponse>(
198+
`${server.serverBaseUrl}/json`,
199+
);
200+
// Select the first target from the page list response
201+
expect(pageListResponse.length).toBeGreaterThanOrEqual(1);
202+
const firstPage = pageListResponse[0];
110203

111-
{
204+
// Build the URL for the debugger
205+
const openUrl = new URL('/open-debugger', server.serverBaseUrl);
206+
openUrl.searchParams.set('launchId', 'launch1');
207+
openUrl.searchParams.set(
208+
'device',
209+
firstPage.reactNative.logicalDeviceId,
210+
);
211+
openUrl.searchParams.set('target', firstPage.id);
212+
213+
// Request to open the debugger for the first device
112214
const response = await requestLocal(openUrl.toString(), {
113215
method: 'POST',
114216
});
217+
115218
// Ensure the request was handled properly
116219
expect(response.statusCode).toBe(200);
117-
}
118-
// Ensure the debugger was launched using the standalone shell API and the same window key
119-
expect(showFuseboxShellSpy).toHaveBeenCalledWith(
120-
expect.any(String),
121-
firstWindowKey,
122-
);
123220

124-
expect(launchDebuggerAppWindowSpy).not.toHaveBeenCalled();
125-
} finally {
126-
device.close();
127-
}
128-
});
221+
// Debugger was launched but will fail to prepare standalone shell
222+
expect(prepareDebuggerShellSpy).toHaveBeenCalled();
223+
224+
// Debugger is not launched with standalone shell since preparation failed
225+
expect(showFuseboxShellSpy).not.toHaveBeenCalled();
129226

130-
// TODO(moti): Add tests around prepareDebuggerShell
227+
// Debugger fallback
228+
expect(launchDebuggerAppWindowSpy).toHaveBeenCalled();
229+
} finally {
230+
device.close();
231+
}
232+
});
233+
});
131234
});
132235
});

0 commit comments

Comments
 (0)