Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 166 additions & 85 deletions packages/dev-middleware/src/__tests__/StandaloneFuseboxShell-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<void> => {});
const unstable_showFuseboxShell = jest.fn(async (_, __): Promise<void> => {});

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<DebuggerShellPreparationResult> => ({
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<JsonPagesListResponse>(
`${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<JsonPagesListResponse>(
`${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<DebuggerShellPreparationResult> => ({
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<JsonPagesListResponse>(
`${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
});
});
Loading