main
js 124 lines 3.42 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 * @emails react-core
8 */
9
10 'use strict';
11
12 let React;
13
14 let ReactDOMClient;
15 let act;
16 let assertLog;
17 let Scheduler;
18
19 describe('ReactDOMSafariMicrotaskBug-test', () => {
20 let container;
21 let overrideQueueMicrotask;
22 let flushFakeMicrotasks;
23
24 beforeEach(() => {
25 // In Safari, microtasks don't always run on clean stack.
26 // This setup crudely approximates it.
27 // In reality, the sync flush happens when an iframe is added to the page.
28 // https://github.com/facebook/react/issues/22459
29 const originalQueueMicrotask = queueMicrotask;
30 overrideQueueMicrotask = false;
31 const fakeMicrotaskQueue = [];
32 global.queueMicrotask = cb => {
33 if (overrideQueueMicrotask) {
34 fakeMicrotaskQueue.push(cb);
35 } else {
36 originalQueueMicrotask(cb);
37 }
38 };
39 flushFakeMicrotasks = () => {
40 while (fakeMicrotaskQueue.length > 0) {
41 const cb = fakeMicrotaskQueue.shift();
42 cb();
43 }
44 };
45
46 jest.resetModules();
47 container = document.createElement('div');
48 React = require('react');
49 ReactDOMClient = require('react-dom/client');
50 act = require('internal-test-utils').act;
51 assertLog = require('internal-test-utils').assertLog;
52 Scheduler = require('scheduler');
53
54 document.body.appendChild(container);
55 });
56
57 afterEach(() => {
58 document.body.removeChild(container);
59 });
60
61 it('should deal with premature microtask in commit phase', async () => {
62 let ran = false;
63 function Foo() {
64 const [state, setState] = React.useState(0);
65 return (
66 <div
67 ref={() => {
68 overrideQueueMicrotask = true;
69 if (!ran) {
70 ran = true;
71 setState(1);
72 flushFakeMicrotasks();
73 Scheduler.log(
74 'Content at end of ref callback: ' + container.textContent,
75 );
76 }
77 }}>
78 {state}
79 </div>
80 );
81 }
82 const root = ReactDOMClient.createRoot(container);
83 await act(() => {
84 root.render(<Foo />);
85 });
86 assertLog(['Content at end of ref callback: 0']);
87 expect(container.textContent).toBe('1');
88 });
89
90 it('should deal with premature microtask in event handler', async () => {
91 function Foo() {
92 const [state, setState] = React.useState(0);
93 return (
94 <button
95 onClick={() => {
96 overrideQueueMicrotask = true;
97 setState(1);
98 flushFakeMicrotasks();
99 Scheduler.log(
100 'Content at end of click handler: ' + container.textContent,
101 );
102 }}>
103 {state}
104 </button>
105 );
106 }
107 const root = ReactDOMClient.createRoot(container);
108 await act(() => {
109 root.render(<Foo />);
110 });
111 expect(container.textContent).toBe('0');
112 await act(() => {
113 container.firstChild.dispatchEvent(
114 new MouseEvent('click', {bubbles: true}),
115 );
116 });
117 // This causes the update to flush earlier than usual. This isn't the ideal
118 // behavior but we use this test to document it. The bug is Safari's, not
119 // ours, so we just do our best to not crash even though the behavior isn't
120 // completely correct.
121 assertLog(['Content at end of click handler: 1']);
122 expect(container.textContent).toBe('1');
123 });
124 });