1import { createInspectorDeviceClass } from '../device';
2import { NetworkResponseHandler } from '../handlers/NetworkResponse';
3import { PageReloadHandler } from '../handlers/PageReload';
4import { VscodeDebuggerGetPossibleBreakpointsHandler } from '../handlers/VscodeDebuggerGetPossibleBreakpoints';
5import { VscodeDebuggerScriptParsedHandler } from '../handlers/VscodeDebuggerScriptParsed';
6import { VscodeDebuggerSetBreakpointByUrlHandler } from '../handlers/VscodeDebuggerSetBreakpointByUrl';
7import { VscodeRuntimeGetPropertiesHandler } from '../handlers/VscodeRuntimeGetProperties';
8import { InspectorHandler } from '../handlers/types';
9
10describe('ExpoInspectorDevice', () => {
11  it('initializes with default handlers', () => {
12    const { device } = createTestDevice();
13
14    function findHandler<T extends Function>(type: T) {
15      return device.handlers.find((handler) => handler instanceof type);
16    }
17
18    expect(findHandler(NetworkResponseHandler)).toBeTruthy();
19    expect(findHandler(PageReloadHandler)).toBeTruthy();
20    expect(findHandler(VscodeDebuggerGetPossibleBreakpointsHandler)).toBeTruthy();
21    expect(findHandler(VscodeDebuggerScriptParsedHandler)).toBeTruthy();
22    expect(findHandler(VscodeDebuggerSetBreakpointByUrlHandler)).toBeTruthy();
23    expect(findHandler(VscodeRuntimeGetPropertiesHandler)).toBeTruthy();
24  });
25
26  describe('device', () => {
27    it('handles known device messages', () => {
28      const { device, MetroDevice } = createTestDevice();
29      const handler: InspectorHandler = { onDeviceMessage: jest.fn().mockReturnValue(true) };
30      device.handlers = [handler];
31
32      device._processMessageFromDevice(
33        { method: 'Network.responseReceived', params: { requestId: 420 } },
34        jest.fn() // debugger info mock
35      );
36
37      expect(handler.onDeviceMessage).toBeCalled();
38      // Expect the message is NOT propagated to original handlers
39      expect(MetroDevice.prototype._processMessageFromDevice).not.toBeCalled();
40    });
41
42    it('does not handle unknown device messages', () => {
43      const { device, MetroDevice } = createTestDevice();
44      const handler: InspectorHandler = { onDeviceMessage: jest.fn().mockReturnValue(false) };
45      device.handlers = [handler];
46
47      device._processMessageFromDevice(
48        { method: 'Network.responseReceived', params: { requestId: 420 } },
49        jest.fn() // debugger info mock
50      );
51
52      expect(handler.onDeviceMessage).toBeCalled();
53      // Expect the message is propagated to original handlers
54      expect(MetroDevice.prototype._processMessageFromDevice).toBeCalled();
55    });
56
57    it('does not handle without handlers', () => {
58      const { device, MetroDevice } = createTestDevice();
59      device.handlers = [];
60
61      device._processMessageFromDevice(
62        { method: 'Network.responseReceived', params: { requestId: 420 } },
63        jest.fn() // debugger info mock
64      );
65
66      // Expect the message is propagated to original handlers
67      expect(MetroDevice.prototype._processMessageFromDevice).toBeCalled();
68    });
69  });
70
71  describe('debugger', () => {
72    it('intercepts known debugger messages', () => {
73      const { device, MetroDevice } = createTestDevice();
74      const handler: InspectorHandler = { onDebuggerMessage: jest.fn().mockReturnValue(true) };
75      device.handlers = [handler];
76
77      const handled = device._interceptMessageFromDebugger(
78        { id: 420, method: 'Network.getResponseBody', params: { requestId: 420 } },
79        jest.fn(), // debugger info mock
80        jest.fn() as any // socket mock
81      );
82
83      expect(handled).toBe(true);
84      expect(handler.onDebuggerMessage).toBeCalled();
85      // Expect the message is NOT propagated to original handlers
86      expect(MetroDevice.prototype._interceptMessageFromDebugger).not.toBeCalled();
87    });
88
89    it('does not intercept unknown debugger messages', () => {
90      const { device, MetroDevice } = createTestDevice();
91      const handler: InspectorHandler = { onDebuggerMessage: jest.fn().mockReturnValue(false) };
92      device.handlers = [handler];
93
94      const handled = device._interceptMessageFromDebugger(
95        { id: 420, method: 'Network.getResponseBody', params: { requestId: 420 } },
96        jest.fn(), // debugger info mock
97        jest.fn() as any // socket mock
98      );
99
100      expect(handled).not.toBe(true);
101      expect(handler.onDebuggerMessage).toBeCalled();
102      // Expect the message is propagated to original handlers
103      expect(MetroDevice.prototype._interceptMessageFromDebugger).toBeCalled();
104    });
105
106    it('does not intercept without handlers', () => {
107      const { device, MetroDevice } = createTestDevice();
108      device.handlers = [];
109
110      const handled = device._interceptMessageFromDebugger(
111        { id: 420, method: 'Network.getResponseBody', params: { requestId: 420 } },
112        jest.fn(), // debugger info mock
113        jest.fn() as any // socket mock
114      );
115
116      expect(handled).not.toBe(true);
117      // Expect the message is propagated to original handlers
118      expect(MetroDevice.prototype._interceptMessageFromDebugger).toBeCalled();
119    });
120  });
121});
122
123/** Create a test device instance without extending the Metro device */
124function createTestDevice() {
125  const metroBundler: any = { broadcastMessage: jest.fn() };
126  class MetroDevice {
127    _processMessageFromDevice() {}
128    _interceptMessageFromDebugger() {}
129  }
130
131  // Dynamically replace these functions with mocks, doesn't work from class declaration
132  MetroDevice.prototype._processMessageFromDevice = jest.fn();
133  MetroDevice.prototype._interceptMessageFromDebugger = jest.fn();
134
135  const ExpoDevice = createInspectorDeviceClass(metroBundler, MetroDevice);
136  const device = new ExpoDevice();
137
138  return { ExpoDevice, MetroDevice, device, metroBundler };
139}
140