diff --git a/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js b/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js index 21de20292df364..71904269f5fa4e 100644 --- a/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js +++ b/packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js @@ -9,7 +9,7 @@ */ import type {JsonPagesListResponse} from '../inspector-proxy/types'; -import type {BrowserLauncher} from '../types/BrowserLauncher'; +import type {DebuggerShellPreparationResult} from '../types/BrowserLauncher'; import DefaultBrowserLauncher from '../utils/DefaultBrowserLauncher'; import {fetchJson, requestLocal} from './FetchUtils'; @@ -22,114 +22,195 @@ const PAGES_POLLING_DELAY = 2100; jest.useFakeTimers(); -describe('enableStandaloneFuseboxShell experiment', () => { - const BrowserLauncherWithFuseboxShell: BrowserLauncher = { - ...DefaultBrowserLauncher, - unstable_showFuseboxShell: () => { - throw new Error('Not implemented'); - }, - unstable_prepareFuseboxShell: async () => { - return {code: 'not_implemented'}; - }, - }; - const serverRef = withServerForEachTest({ - logger: undefined, - unstable_browserLauncher: BrowserLauncherWithFuseboxShell, - unstable_experiments: { - enableStandaloneFuseboxShell: true, +async function setupDevice( + serverRef: {+serverBaseWsUrl: string, ...}, + signal: AbortSignal, +) { + const device = await createDeviceMock( + `${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`, + signal, + ); + device.getPages.mockImplementation(() => [ + { + app: 'bar-app', + id: 'page1', + title: 'bar-title', + vm: 'bar-vm', + capabilities: { + nativePageReloads: true, + prefersFuseboxFrontend: true, + }, }, - }); + ]); + jest.advanceTimersByTime(PAGES_POLLING_DELAY); + + return device; +} + +describe('enableStandaloneFuseboxShell experiment', () => { + const launchDebuggerAppWindow = jest.fn(async (_): Promise => {}); + const unstable_showFuseboxShell = jest.fn(async (_, __): Promise => {}); + const autoCleanup = withAbortSignalForEachTest(); + afterEach(() => { jest.clearAllMocks(); }); describe('/open-debugger endpoint', () => { - test('launches the shell with a frontend URL and stable window key', async () => { - // Connect a device to use when opening the debugger - const device = await createDeviceMock( - `${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`, - autoCleanup.signal, + describe('success', () => { + const unstable_prepareFuseboxShell = jest.fn( + async (): Promise => ({ + code: 'not_implemented', + }), ); - device.getPages.mockImplementation(() => [ - { - app: 'bar-app', - id: 'page1', - title: 'bar-title', - vm: 'bar-vm', - capabilities: { - // Ensure the device target can be found when launching the debugger - nativePageReloads: true, - // Mark as Fusebox - prefersFuseboxFrontend: true, - }, + const successfulServer = withServerForEachTest({ + logger: undefined, + unstable_browserLauncher: { + ...DefaultBrowserLauncher, + launchDebuggerAppWindow, + unstable_showFuseboxShell, + unstable_prepareFuseboxShell, + }, + unstable_experiments: { + enableStandaloneFuseboxShell: true, }, - ]); - jest.advanceTimersByTime(PAGES_POLLING_DELAY); - - const launchDebuggerAppWindowSpy = jest - .spyOn(BrowserLauncherWithFuseboxShell, 'launchDebuggerAppWindow') - .mockResolvedValue(); - const showFuseboxShellSpy = jest - .spyOn(BrowserLauncherWithFuseboxShell, 'unstable_showFuseboxShell') - .mockResolvedValue(); - - try { - // Fetch the target information for the device - const pageListResponse = await fetchJson( - `${serverRef.serverBaseUrl}/json`, - ); - // Select the first target from the page list response - expect(pageListResponse.length).toBeGreaterThanOrEqual(1); - const firstPage = pageListResponse[0]; - - // Build the URL for the debugger - const openUrl = new URL('/open-debugger', serverRef.serverBaseUrl); - openUrl.searchParams.set('launchId', 'launch1'); - openUrl.searchParams.set( - 'device', - firstPage.reactNative.logicalDeviceId, - ); - openUrl.searchParams.set('target', firstPage.id); - // Request to open the debugger for the first device - { + }); + + test('launches the shell with a frontend URL and stable window key', async () => { + // Connect a device to use when opening the debugger + const device = await setupDevice(successfulServer, autoCleanup.signal); + + try { + // Fetch the target information for the device + const pageListResponse = await fetchJson( + `${successfulServer.serverBaseUrl}/json`, + ); + // Select the first target from the page list response + expect(pageListResponse.length).toBeGreaterThanOrEqual(1); + const firstPage = pageListResponse[0]; + + // Build the URL for the debugger + const openUrl = new URL( + '/open-debugger', + successfulServer.serverBaseUrl, + ); + openUrl.searchParams.set('launchId', 'launch1'); + openUrl.searchParams.set( + 'device', + firstPage.reactNative.logicalDeviceId, + ); + openUrl.searchParams.set('target', firstPage.id); + // Request to open the debugger for the first device const response = await requestLocal(openUrl.toString(), { method: 'POST', }); + // Ensure the request was handled properly expect(response.statusCode).toBe(200); + + // Ensure the debugger was launched using the standalone shell API + expect(unstable_showFuseboxShell).toHaveBeenCalledWith( + expect.any(String), + expect.any(String), + ); + + // No call to the regular browser launcher since standalone shell should be used + expect(launchDebuggerAppWindow).not.toHaveBeenCalled(); + + const firstWindowKey = unstable_showFuseboxShell.mock.calls[0][1]; + + unstable_showFuseboxShell.mockClear(); + openUrl.searchParams.set('launchId', 'launch2'); + + const anotherResponse = await requestLocal(openUrl.toString(), { + method: 'POST', + }); + + // Ensure the request was handled properly + expect(anotherResponse.statusCode).toBe(200); + + // Ensure the debugger was launched using the standalone shell API and the same window key + expect(unstable_showFuseboxShell).toHaveBeenCalledWith( + expect.any(String), + firstWindowKey, + ); + + // Ensure the debugger preparation function was called, just one time, during middleware initialization + expect(unstable_prepareFuseboxShell).toHaveBeenCalledTimes(1); + + // No fallback needed + expect(launchDebuggerAppWindow).not.toHaveBeenCalled(); + } finally { + device.close(); } - openUrl.searchParams.set('launchId', 'launch1'); + }); + }); + + describe('unstable_prepareFuseboxShell failures', () => { + const unstable_prepareFuseboxShell = jest.fn( + async (): Promise => ({ + code: 'platform_not_supported', + }), + ); + const failingServerRef = withServerForEachTest({ + logger: undefined, + unstable_browserLauncher: { + ...DefaultBrowserLauncher, + launchDebuggerAppWindow, + unstable_showFuseboxShell, + unstable_prepareFuseboxShell, + }, + unstable_experiments: { + enableStandaloneFuseboxShell: true, + }, + }); - // Ensure the debugger was launched using the standalone shell API - expect(showFuseboxShellSpy).toHaveBeenCalledWith( - expect.any(String), - expect.any(String), - ); - const firstWindowKey = showFuseboxShellSpy.mock.calls[0][1]; + test('falls back to browser window when preparation fails', async () => { + // Connect a device to use when opening the debugger + const device = await setupDevice(failingServerRef, autoCleanup.signal); - showFuseboxShellSpy.mockClear(); - openUrl.searchParams.set('launchId', 'launch2'); + try { + // Fetch the target information for the device + const pageListResponse = await fetchJson( + `${failingServerRef.serverBaseUrl}/json`, + ); + // Select the first target from the page list response + expect(pageListResponse.length).toBeGreaterThanOrEqual(1); + const firstPage = pageListResponse[0]; - { + // Build the URL for the debugger + const openUrl = new URL( + '/open-debugger', + failingServerRef.serverBaseUrl, + ); + openUrl.searchParams.set('launchId', 'launch1'); + openUrl.searchParams.set( + 'device', + firstPage.reactNative.logicalDeviceId, + ); + openUrl.searchParams.set('target', firstPage.id); + + // Request to open the debugger for the first device const response = await requestLocal(openUrl.toString(), { method: 'POST', }); + // Ensure the request was handled properly expect(response.statusCode).toBe(200); + + // Debugger was launched but will fail to prepare standalone shell + expect(unstable_prepareFuseboxShell).toHaveBeenCalled(); + + // Debugger is not launched with standalone shell since preparation failed + expect(unstable_showFuseboxShell).not.toHaveBeenCalled(); + + // Debugger fallback + expect(launchDebuggerAppWindow).toHaveBeenCalled(); + } finally { + device.close(); } - // Ensure the debugger was launched using the standalone shell API and the same window key - expect(showFuseboxShellSpy).toHaveBeenCalledWith( - expect.any(String), - firstWindowKey, - ); - - expect(launchDebuggerAppWindowSpy).not.toHaveBeenCalled(); - } finally { - device.close(); - } + }); }); - - // TODO(moti): Add tests around unstable_prepareFuseboxShell }); });