mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53076 Changelog: [Internal] Reviewed By: marcoww6 Differential Revision: D79672242 fbshipit-source-id: 560f057d8658ed602cf7241e584bade70d8f3a99
394 lines
12 KiB
JavaScript
394 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,
|
|
projectRoot: '',
|
|
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,
|
|
projectRoot: '',
|
|
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,
|
|
projectRoot: '',
|
|
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,
|
|
projectRoot: '',
|
|
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,
|
|
projectRoot: '',
|
|
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,
|
|
projectRoot: '',
|
|
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,
|
|
projectRoot: '',
|
|
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()));
|
|
}
|