Files
react-native/packages/dev-middleware/src/__tests__/InspectorProxyCustomMessageHandler-test.js
T
Vitali Zaidman 3a954caf4f don't require projectRoot in devMiddleware (#53921)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53921

Changelog: [Internal] `devMiddleware` removed the unused option `projectRoot`

This option was used only in a dead branch of the code inside `InspectorProxy`.

The scenario where it appeared was enabling resolving script sources (`Debugger.getScriptSource`) starting with `file://` which were coming from the file system. However, we don't seem to ever point scripts at the filesystem this way.

Reviewed By: huntie

Differential Revision: D82739662

fbshipit-source-id: 9a130eaa83cb94ae0e36a5a1102c56ff8a36cffc
2025-09-24 12:47:24 -07:00

387 lines
12 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import {fetchJson} from './FetchUtils';
import {createDebuggerMock} from './InspectorDebuggerUtils';
import {createDeviceMock} from './InspectorDeviceUtils';
import {createAndConnectTarget} from './InspectorProtocolUtils';
import {withAbortSignalForEachTest} from './ResourceUtils';
import {baseUrlForServer, createServer} from './ServerUtils';
import invariant from 'invariant';
import until from 'wait-for-expect';
// WebSocket is unreliable when using fake timers.
jest.useRealTimers();
jest.setTimeout(10000);
describe('inspector proxy device message middleware', () => {
const autoCleanup = withAbortSignalForEachTest();
const page = {
id: 'page1',
app: 'bar-app',
title: 'bar-title',
vm: 'bar-vm',
};
afterEach(() => {
jest.clearAllMocks();
});
test('middleware is created with device, debugger, and page information', async () => {
const createCustomMessageHandler = jest.fn().mockImplementation(() => null);
const {server} = await createServer({
logger: undefined,
unstable_customInspectorMessageHandler: createCustomMessageHandler,
});
let device, debugger_;
try {
({device, debugger_} = await createAndConnectTarget(
serverRefUrls(server),
autoCleanup.signal,
page,
));
// Ensure the middleware was created with the device information
await until(() =>
expect(createCustomMessageHandler).toBeCalledWith(
expect.objectContaining({
page: expect.objectContaining({
...page,
capabilities: expect.any(Object),
}),
device: expect.objectContaining({
appId: expect.any(String),
id: expect.any(String),
name: expect.any(String),
sendMessage: expect.any(Function),
}),
debugger: expect.objectContaining({
userAgent: null,
sendMessage: expect.any(Function),
}),
}),
),
);
} finally {
device?.close();
debugger_?.close();
await closeServer(server);
}
});
test('middleware is created with device, debugger, and page information for synthetic reloadable page', async () => {
const createCustomMessageHandler = jest.fn().mockImplementation(() => null);
const {server} = await createServer({
logger: undefined,
unstable_customInspectorMessageHandler: createCustomMessageHandler,
});
const {serverBaseUrl, serverBaseWsUrl} = serverRefUrls(server);
let device, debugger_;
try {
device = await createDeviceMock(
`${serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`,
autoCleanup.signal,
);
// Mock the device to return a normal React (Native) page
device.getPages.mockImplementation(() => [
{
...page,
// NOTE: 'React' is a magic string used to detect React Native pages.
title: 'React Native (mock)',
},
]);
// Retrieve the full page list from device
let pageList;
await until(async () => {
pageList = (await fetchJson(
`${serverBaseUrl}/json`,
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
invariant(pageList != null, '');
// Find the synthetic page
const syntheticPage = pageList.find(
({title}) =>
// NOTE: Magic string used for the synthetic page that has a stable ID
title === 'React Native Experimental (Improved Chrome Reloads)',
);
expect(syntheticPage).not.toBeUndefined();
// Connect the debugger to this synthetic page
debugger_ = await createDebuggerMock(
syntheticPage.webSocketDebuggerUrl,
autoCleanup.signal,
);
// Ensure the middleware was created with the device information
await until(() =>
expect(createCustomMessageHandler).toBeCalledWith(
expect.objectContaining({
page: expect.objectContaining({
id: expect.any(String),
title: syntheticPage.title,
vm: syntheticPage.vm,
app: expect.any(String),
capabilities: expect.any(Object),
}),
device: expect.objectContaining({
appId: expect.any(String),
id: expect.any(String),
name: expect.any(String),
sendMessage: expect.any(Function),
}),
debugger: expect.objectContaining({
userAgent: null,
sendMessage: expect.any(Function),
}),
}),
),
);
} finally {
device?.close();
debugger_?.close();
await closeServer(server);
}
});
test('send message functions are passing messages to sockets', async () => {
const handleDebuggerMessage = jest.fn();
const handleDeviceMessage = jest.fn();
const createCustomMessageHandler = jest.fn().mockImplementation(() => ({
handleDebuggerMessage,
handleDeviceMessage,
}));
const {server} = await createServer({
logger: undefined,
unstable_customInspectorMessageHandler: createCustomMessageHandler,
});
let device, debugger_;
try {
({device, debugger_} = await createAndConnectTarget(
serverRefUrls(server),
autoCleanup.signal,
page,
));
// Ensure the middleware was created with the send message methods
await until(() =>
expect(createCustomMessageHandler).toBeCalledWith(
expect.objectContaining({
device: expect.objectContaining({
sendMessage: expect.any(Function),
}),
debugger: expect.objectContaining({
sendMessage: expect.any(Function),
}),
}),
),
);
// Send a message to the device
createCustomMessageHandler.mock.calls[0][0].device.sendMessage({
id: 1,
});
// Ensure the device received the message
await until(() =>
expect(device.wrappedEvent).toBeCalledWith({
event: 'wrappedEvent',
payload: {
pageId: page.id,
wrappedEvent: JSON.stringify({id: 1}),
},
}),
);
// Send a message to the debugger
createCustomMessageHandler.mock.calls[0][0].debugger.sendMessage({
id: 2,
});
// Ensure the debugger received the message
await until(() =>
expect(debugger_.handle).toBeCalledWith({
id: 2,
}),
);
} finally {
device?.close();
debugger_?.close();
await closeServer(server);
}
});
test('device message is passed to message middleware', async () => {
const handleDeviceMessage = jest.fn();
const {server} = await createServer({
logger: undefined,
unstable_customInspectorMessageHandler: () => ({
handleDeviceMessage,
handleDebuggerMessage() {},
}),
});
let device, debugger_;
try {
({device, debugger_} = await createAndConnectTarget(
serverRefUrls(server),
autoCleanup.signal,
page,
));
// Send a message from the device, and ensure the middleware received it
device.sendWrappedEvent(page.id, {id: 1337});
// Ensure the debugger received the message
await until(() => expect(debugger_.handle).toBeCalledWith({id: 1337}));
// Ensure the middleware received the message
await until(() => expect(handleDeviceMessage).toBeCalled());
} finally {
device?.close();
debugger_?.close();
await closeServer(server);
}
});
test('device message stops propagating when handled by middleware', async () => {
const handleDeviceMessage = jest.fn();
const {server} = await createServer({
logger: undefined,
unstable_customInspectorMessageHandler: () => ({
handleDeviceMessage,
handleDebuggerMessage() {},
}),
});
let device, debugger_;
try {
({device, debugger_} = await createAndConnectTarget(
serverRefUrls(server),
autoCleanup.signal,
page,
));
// Stop the first message from propagating by returning true (once) from middleware
handleDeviceMessage.mockReturnValueOnce(true);
// Send the first message which should NOT be received by the debugger
device.sendWrappedEvent(page.id, {id: -1});
await until(() => expect(handleDeviceMessage).toBeCalled());
// Send the second message which should be received by the debugger
device.sendWrappedEvent(page.id, {id: 1337});
// Ensure only the last message was received by the debugger
await until(() => expect(debugger_.handle).toBeCalledWith({id: 1337}));
// Ensure the first message was not received by the debugger
expect(debugger_.handle).not.toBeCalledWith({id: -1});
} finally {
device?.close();
debugger_?.close();
await closeServer(server);
}
});
test('debugger message is passed to message middleware', async () => {
const handleDebuggerMessage = jest.fn();
const {server} = await createServer({
logger: undefined,
unstable_customInspectorMessageHandler: () => ({
handleDeviceMessage() {},
handleDebuggerMessage,
}),
});
let device, debugger_;
try {
({device, debugger_} = await createAndConnectTarget(
serverRefUrls(server),
autoCleanup.signal,
page,
));
// Send a message from the debugger
const message = {
method: 'Runtime.enable',
id: 1337,
};
debugger_.send(message);
// Ensure the device received the message
await until(() => expect(device.wrappedEvent).toBeCalled());
// Ensure the middleware received the message
await until(() => expect(handleDebuggerMessage).toBeCalledWith(message));
} finally {
device?.close();
debugger_?.close();
await closeServer(server);
}
});
test('debugger message stops propagating when handled by middleware', async () => {
const handleDebuggerMessage = jest.fn();
const {server} = await createServer({
logger: undefined,
unstable_customInspectorMessageHandler: () => ({
handleDeviceMessage() {},
handleDebuggerMessage,
}),
});
let device, debugger_;
try {
({device, debugger_} = await createAndConnectTarget(
serverRefUrls(server),
autoCleanup.signal,
page,
));
// Stop the first message from propagating by returning true (once) from middleware
handleDebuggerMessage.mockReturnValueOnce(true);
// Send the first emssage which should not be received by the device
debugger_.send({id: -1});
// Send the second message which should be received by the device
debugger_.send({id: 1337});
// Ensure only the last message was received by the device
await until(() =>
expect(device.wrappedEvent).toBeCalledWith({
event: 'wrappedEvent',
payload: {pageId: page.id, wrappedEvent: JSON.stringify({id: 1337})},
}),
);
// Ensure the first message was not received by the device
expect(device.wrappedEvent).not.toBeCalledWith({id: -1});
} finally {
device?.close();
debugger_?.close();
await closeServer(server);
}
});
});
function serverRefUrls(server: http$Server | https$Server) {
return {
serverBaseUrl: baseUrlForServer(server, 'http'),
serverBaseWsUrl: baseUrlForServer(server, 'ws'),
};
}
async function closeServer(server: http$Server | https$Server): Promise<void> {
return new Promise(resolve => server.close(() => resolve()));
}