main
js 164 lines 4.96 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 import {getVersionedRenderImplementation} from './utils';
11
12 jest.mock('react-devtools-feature-flags', () => ({
13 ...jest.requireActual('react-devtools-feature-flags'),
14 enableLogger: true,
15 }));
16
17 describe('error reporting', () => {
18 let React;
19 let act;
20
21 beforeEach(() => {
22 React = require('react');
23 act = require('./utils').act;
24 });
25
26 const {getContainer, render, unmount} = getVersionedRenderImplementation();
27
28 it('reports a Store error before the frontend mounts', () => {
29 const {registerEventLogger} = require('react-devtools-shared/src/Logger');
30 const {
31 subscribeToStoreErrors,
32 } = require('react-devtools-shared/src/devtools/storeErrorLogger');
33 const eventLogger = jest.fn();
34 const unregisterEventLogger = registerEventLogger(eventLogger);
35 const unsubscribeFromStoreErrors = subscribeToStoreErrors(
36 global.store,
37 global.bridge,
38 );
39 const error = new Error('Initial render error');
40
41 try {
42 expect(() => global.store._throwAndEmitError(error)).toThrow(error);
43 expect(eventLogger).toHaveBeenCalledTimes(1);
44 expect(eventLogger).toHaveBeenCalledWith({
45 event_name: 'error',
46 error_message: error.message,
47 error_stack: error.stack,
48 error_component_stack: null,
49 });
50 } finally {
51 unsubscribeFromStoreErrors();
52 unregisterEventLogger();
53 }
54 });
55
56 it('reports a Store error once when multiple error boundaries observe it', () => {
57 const {registerEventLogger} = require('react-devtools-shared/src/Logger');
58 const ErrorBoundary =
59 require('react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary').default;
60 const {
61 subscribeToStoreErrors,
62 } = require('react-devtools-shared/src/devtools/storeErrorLogger');
63 const store = global.store;
64 const eventLogger = jest.fn();
65 const unregisterEventLogger = registerEventLogger(eventLogger);
66 const unsubscribeFromStoreErrors = subscribeToStoreErrors(
67 store,
68 global.bridge,
69 );
70
71 act(() => {
72 render(
73 <>
74 <ErrorBoundary store={store}>First boundary</ErrorBoundary>
75 <ErrorBoundary store={store}>Second boundary</ErrorBoundary>
76 <ErrorBoundary store={store}>Third boundary</ErrorBoundary>
77 </>,
78 );
79 });
80
81 const error = new Error('Store error');
82 try {
83 act(() => {
84 expect(() => store._throwAndEmitError(error)).toThrow(error);
85 });
86
87 expect(eventLogger).toHaveBeenCalledTimes(1);
88 expect(eventLogger).toHaveBeenCalledWith({
89 event_name: 'error',
90 error_message: error.message,
91 error_stack: error.stack,
92 error_component_stack: null,
93 });
94 expect(
95 getContainer().textContent.match(/Uncaught Error: Store error/g),
96 ).toHaveLength(3);
97 } finally {
98 unsubscribeFromStoreErrors();
99 unregisterEventLogger();
100 act(() => unmount());
101 }
102 });
103
104 it('registers the event logger once while its iframe is loading', () => {
105 const Logger = require('react-devtools-shared/src/Logger');
106 const registerEventLogger = jest.spyOn(Logger, 'registerEventLogger');
107 const loggingURL = 'https://example.com/react-devtools-logging';
108 const previousLoggingURL = process.env.LOGGING_URL;
109 process.env.LOGGING_URL = loggingURL;
110
111 try {
112 const {
113 registerDevToolsEventLogger,
114 } = require('react-devtools-shared/src/registerDevToolsEventLogger');
115
116 registerDevToolsEventLogger('test');
117 registerDevToolsEventLogger('test');
118
119 expect(registerEventLogger).toHaveBeenCalledTimes(1);
120 expect(
121 document.querySelectorAll(`iframe[src="${loggingURL}"]`),
122 ).toHaveLength(1);
123 } finally {
124 registerEventLogger.mockRestore();
125 if (previousLoggingURL === undefined) {
126 delete process.env.LOGGING_URL;
127 } else {
128 process.env.LOGGING_URL = previousLoggingURL;
129 }
130 document
131 .querySelectorAll(`iframe[src="${loggingURL}"]`)
132 .forEach(iframe => iframe.remove());
133 }
134 });
135
136 it('normalizes values that are not Error objects', () => {
137 const {
138 logErrorEvent,
139 registerEventLogger,
140 } = require('react-devtools-shared/src/Logger');
141 const eventLogger = jest.fn();
142 const unregisterEventLogger = registerEventLogger(eventLogger);
143
144 try {
145 logErrorEvent(null, null);
146 logErrorEvent({message: 42, stack: {}}, null);
147
148 expect(eventLogger).toHaveBeenNthCalledWith(1, {
149 event_name: 'error',
150 error_message: null,
151 error_stack: null,
152 error_component_stack: null,
153 });
154 expect(eventLogger).toHaveBeenNthCalledWith(2, {
155 event_name: 'error',
156 error_message: null,
157 error_stack: null,
158 error_component_stack: null,
159 });
160 } finally {
161 unregisterEventLogger();
162 }
163 });
164 });