main
js 166 lines 5.13 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('events', () => {
11 let dispatcher;
12
13 beforeEach(() => {
14 const EventEmitter = require('../events').default;
15
16 dispatcher = new EventEmitter();
17 });
18
19 it('can dispatch an event with no listeners', () => {
20 dispatcher.emit('event', 123);
21 });
22
23 it('handles a listener being attached multiple times', () => {
24 const callback = jest.fn();
25
26 dispatcher.addListener('event', callback);
27 dispatcher.addListener('event', callback);
28
29 dispatcher.emit('event', 123);
30 expect(callback).toHaveBeenCalledTimes(1);
31 expect(callback).toHaveBeenCalledWith(123);
32 });
33
34 it('notifies all attached listeners of events', () => {
35 const callback1 = jest.fn();
36 const callback2 = jest.fn();
37 const callback3 = jest.fn();
38
39 dispatcher.addListener('event', callback1);
40 dispatcher.addListener('event', callback2);
41 dispatcher.addListener('other-event', callback3);
42 dispatcher.emit('event', 123);
43
44 expect(callback1).toHaveBeenCalledTimes(1);
45 expect(callback1).toHaveBeenCalledWith(123);
46 expect(callback2).toHaveBeenCalledTimes(1);
47 expect(callback2).toHaveBeenCalledWith(123);
48 expect(callback3).not.toHaveBeenCalled();
49 });
50
51 it('calls later listeners before re-throwing if an earlier one throws', () => {
52 const callbackThatThrows = jest.fn(() => {
53 throw Error('expected');
54 });
55 const callback = jest.fn();
56
57 dispatcher.addListener('event', callbackThatThrows);
58 dispatcher.addListener('event', callback);
59
60 expect(() => {
61 dispatcher.emit('event', 123);
62 }).toThrow('expected');
63
64 expect(callbackThatThrows).toHaveBeenCalledTimes(1);
65 expect(callbackThatThrows).toHaveBeenCalledWith(123);
66 expect(callback).toHaveBeenCalledTimes(1);
67 expect(callback).toHaveBeenCalledWith(123);
68 });
69
70 it('preserves the first thrown value and reports later errors', () => {
71 const laterError = new Error('later error');
72 const errorHandler = jest.fn(event => {
73 event.preventDefault();
74 });
75 const firstCallback = jest.fn(() => {
76 // This verifies that the emitter preserves any legal thrown value.
77 // eslint-disable-next-line no-throw-literal
78 throw null;
79 });
80 const secondCallback = jest.fn(() => {
81 throw laterError;
82 });
83 const thirdCallback = jest.fn();
84
85 dispatcher.addListener('event', firstCallback);
86 dispatcher.addListener('event', secondCallback);
87 dispatcher.addListener('event', thirdCallback);
88
89 let caughtValue = undefined;
90 window.addEventListener('error', errorHandler);
91 try {
92 dispatcher.emit('event', 123);
93 } catch (error) {
94 caughtValue = error;
95 } finally {
96 window.removeEventListener('error', errorHandler);
97 }
98
99 expect(caughtValue).toBe(null);
100 expect(errorHandler).toHaveBeenCalledTimes(1);
101 expect(errorHandler.mock.calls[0][0]).toEqual(
102 expect.objectContaining({
103 error: laterError,
104 message: 'later error',
105 }),
106 );
107 expect(thirdCallback).toHaveBeenCalledWith(123);
108 });
109
110 it('removes attached listeners', () => {
111 const callback1 = jest.fn();
112 const callback2 = jest.fn();
113
114 dispatcher.addListener('event', callback1);
115 dispatcher.addListener('other-event', callback2);
116 dispatcher.removeListener('event', callback1);
117 dispatcher.emit('event', 123);
118 expect(callback1).not.toHaveBeenCalled();
119 dispatcher.emit('other-event', 123);
120 expect(callback2).toHaveBeenCalledTimes(1);
121 expect(callback2).toHaveBeenCalledWith(123);
122 });
123
124 it('removes all listeners', () => {
125 const callback1 = jest.fn();
126 const callback2 = jest.fn();
127 const callback3 = jest.fn();
128
129 dispatcher.addListener('event', callback1);
130 dispatcher.addListener('event', callback2);
131 dispatcher.addListener('other-event', callback3);
132 dispatcher.removeAllListeners();
133 dispatcher.emit('event', 123);
134 dispatcher.emit('other-event', 123);
135
136 expect(callback1).not.toHaveBeenCalled();
137 expect(callback2).not.toHaveBeenCalled();
138 expect(callback3).not.toHaveBeenCalled();
139 });
140
141 it('should call the initial listeners even if others are added or removed during a dispatch', () => {
142 const callback1 = jest.fn(() => {
143 dispatcher.removeListener('event', callback2);
144 dispatcher.addListener('event', callback3);
145 });
146 const callback2 = jest.fn();
147 const callback3 = jest.fn();
148
149 dispatcher.addListener('event', callback1);
150 dispatcher.addListener('event', callback2);
151
152 dispatcher.emit('event', 123);
153 expect(callback1).toHaveBeenCalledTimes(1);
154 expect(callback1).toHaveBeenCalledWith(123);
155 expect(callback2).toHaveBeenCalledTimes(1);
156 expect(callback2).toHaveBeenCalledWith(123);
157 expect(callback3).not.toHaveBeenCalled();
158
159 dispatcher.emit('event', 456);
160 expect(callback1).toHaveBeenCalledTimes(2);
161 expect(callback1).toHaveBeenCalledWith(456);
162 expect(callback2).toHaveBeenCalledTimes(1);
163 expect(callback3).toHaveBeenCalledTimes(1);
164 expect(callback3).toHaveBeenCalledWith(456);
165 });
166 });