main
js 125 lines 3.8 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 describe('Bridge', () => {
11 let Bridge;
12
13 beforeEach(() => {
14 Bridge = require('react-devtools-shared/src/bridge').default;
15 });
16
17 // @reactVersion >=16.0
18 it('should shutdown properly', () => {
19 const wall = {
20 listen: jest.fn(() => () => {}),
21 send: jest.fn(),
22 };
23 const bridge = new Bridge(wall);
24 const shutdownCallback = jest.fn();
25 bridge.addListener('shutdown', shutdownCallback);
26
27 // Check that we're wired up correctly.
28 bridge.send('reloadAppForProfiling');
29 jest.runAllTimers();
30 expect(wall.send).toHaveBeenCalledWith('reloadAppForProfiling', undefined);
31
32 // Should flush pending messages and then shut down.
33 wall.send.mockClear();
34 bridge.send('update', '1');
35 bridge.send('update', '2');
36 bridge.shutdown();
37 jest.runAllTimers();
38 expect(wall.send).toHaveBeenCalledWith('update', '1');
39 expect(wall.send).toHaveBeenCalledWith('update', '2');
40 expect(wall.send).toHaveBeenCalledWith('shutdown', undefined);
41 expect(shutdownCallback).toHaveBeenCalledTimes(1);
42
43 // Using a Bridge after shutdown is a lifecycle error.
44 wall.send.mockClear();
45 expect(() => bridge.send('should not send')).toThrow(
46 'Cannot send a message through a Bridge that has been shut down.',
47 );
48 expect(() => bridge.addListener('event', () => {})).toThrow(
49 'Cannot add a listener through a Bridge that has been shut down.',
50 );
51 expect(() => bridge.emit('event')).toThrow(
52 'Cannot emit an event through a Bridge that has been shut down.',
53 );
54 expect(() => bridge.shutdown()).toThrow(
55 'Cannot shut down through a Bridge that has been shut down.',
56 );
57 jest.runAllTimers();
58 expect(wall.send).not.toHaveBeenCalled();
59 });
60
61 // @reactVersion >=16.0
62 it('validates messages received from the wall', () => {
63 let wallListener: ((message: mixed) => void) | null = null;
64 const wall = {
65 listen: jest.fn(listener => {
66 wallListener = listener;
67 return () => {};
68 }),
69 send: jest.fn(),
70 };
71 const bridge = new Bridge(wall);
72 const listener = jest.fn();
73 bridge.addListener('event', listener);
74
75 const dispatch = (message: mixed) => {
76 if (wallListener === null) {
77 throw new Error('Expected the Bridge to subscribe to the wall.');
78 }
79 wallListener(message);
80 };
81
82 // Walls may share their transport with unrelated or legacy messages.
83 dispatch(null);
84 dispatch({type: 'event'});
85 expect(listener).not.toHaveBeenCalled();
86
87 expect(() => dispatch({event: 123})).toThrow(
88 'Bridge event names must be non-empty strings.',
89 );
90 expect(() => dispatch({event: ''})).toThrow(
91 'Bridge event names must be non-empty strings.',
92 );
93
94 dispatch({event: 'event', payload: 123});
95 expect(listener).toHaveBeenCalledWith(123);
96 });
97
98 // @reactVersion >=16.0
99 it('requires Wall.listen to return a cleanup function', () => {
100 expect(
101 () =>
102 new Bridge({
103 listen: () => undefined,
104 send: jest.fn(),
105 }),
106 ).toThrow('Wall.listen() must return an unlisten function.');
107 });
108
109 // @reactVersion >=16.0
110 it('flushes pending messages when wall cleanup throws', () => {
111 const expectedError = new Error('Failed to unsubscribe');
112 const wall = {
113 listen: jest.fn(() => () => {
114 throw expectedError;
115 }),
116 send: jest.fn(),
117 };
118 const bridge = new Bridge(wall);
119
120 bridge.send('update', 'value');
121 expect(() => bridge.shutdown()).toThrow(expectedError);
122 expect(wall.send).toHaveBeenCalledWith('update', 'value');
123 expect(wall.send).toHaveBeenCalledWith('shutdown', undefined);
124 });
125 });