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
+describe('ReactStartTransitionMultipleRenderers', () => {
13
+ let act;
14
+ let container;
15
+ let React;
16
+ let ReactDOMClient;
17
+ let Scheduler;
18
+ let assertLog;
19
+ let startTransition;
20
+ let useOptimistic;
21
+ let textCache;
22
+
23
+ beforeEach(() => {
24
+ jest.resetModules();
25
+ React = require('react');
26
+ ReactDOMClient = require('react-dom/client');
27
+ Scheduler = require('scheduler');
28
+ act = require('internal-test-utils').act;
29
+ assertLog = require('internal-test-utils').assertLog;
30
+ startTransition = React.startTransition;
31
+ useOptimistic = React.useOptimistic;
32
+ container = document.createElement('div');
33
+ document.body.appendChild(container);
34
+
35
+ textCache = new Map();
36
+ });
37
+
38
+ function resolveText(text) {
39
+ const record = textCache.get(text);
40
+ if (record === undefined) {
41
+ const newRecord = {
42
+ status: 'resolved',
43
+ value: text,
44
+ };
45
+ textCache.set(text, newRecord);
46
+ } else if (record.status === 'pending') {
47
+ const thenable = record.value;
48
+ record.status = 'resolved';
49
+ record.value = text;
50
+ thenable.pings.forEach(t => t(text));
51
+ }
52
+ }
53
+
54
+ function getText(text) {
55
+ const record = textCache.get(text);
56
+ if (record === undefined) {
57
+ const thenable = {
58
+ pings: [],
59
+ then(resolve) {
60
+ if (newRecord.status === 'pending') {
61
+ thenable.pings.push(resolve);
62
+ } else {
63
+ Promise.resolve().then(() => resolve(newRecord.value));
64
+ }
65
+ },
66
+ };
67
+ const newRecord = {
68
+ status: 'pending',
69
+ value: thenable,
70
+ };
71
+ textCache.set(text, newRecord);
72
+ return thenable;
73
+ } else {
74
+ switch (record.status) {
75
+ case 'pending':
76
+ return record.value;
77
+ case 'rejected':
78
+ return Promise.reject(record.value);
79
+ case 'resolved':
80
+ return Promise.resolve(record.value);
81
+ }
82
+ }
83
+ }
84
+
85
+ function Text({text}) {
86
+ Scheduler.log(text);
87
+ return text;
88
+ }
89
+
90
+ afterEach(() => {
91
+ document.body.removeChild(container);
92
+ });
93
+
94
+ // This test imports multiple reconcilers. Because of how the renderers are
95
+ // built, it only works when running tests using the actual build artifacts,
96
+ // not the source files.
97
+ // @gate !source
98
+ test('React.startTransition works across multiple renderers', async () => {
99
+ const ReactNoop = require('react-noop-renderer');
100
+
101
+ const setIsPendings = new Set();
102
+
103
+ function App() {
104
+ const [isPending, setIsPending] = useOptimistic(false);
105
+ setIsPendings.add(setIsPending);
106
+ return <Text text={isPending ? 'Pending' : 'Not pending'} />;
107
+ }
108
+
109
+ const noopRoot = ReactNoop.createRoot(null);
110
+ const domRoot = ReactDOMClient.createRoot(container);
111
+
112
+ // Run the same component in two separate renderers.
113
+ await act(() => {
114
+ noopRoot.render(<App />);
115
+ domRoot.render(<App />);
116
+ });
117
+ assertLog(['Not pending', 'Not pending']);
118
+ expect(container.textContent).toEqual('Not pending');
119
+ expect(noopRoot).toMatchRenderedOutput('Not pending');
120
+
121
+ await act(() => {
122
+ startTransition(async () => {
123
+ // Wait until after an async gap before setting the optimistic state.
124
+ await getText('Wait before setting isPending');
125
+ setIsPendings.forEach(setIsPending => setIsPending(true));
126
+
127
+ // The optimistic state should not be reverted until the
128
+ // action completes.
129
+ await getText('Wait until end of async action');
130
+ });
131
+ });
132
+
133
+ await act(() => resolveText('Wait before setting isPending'));
134
+ assertLog(['Pending', 'Pending']);
135
+ expect(container.textContent).toEqual('Pending');
136
+ expect(noopRoot).toMatchRenderedOutput('Pending');
137
+
138
+ await act(() => resolveText('Wait until end of async action'));
139
+ assertLog(['Not pending', 'Not pending']);
140
+ expect(container.textContent).toEqual('Not pending');
141
+ expect(noopRoot).toMatchRenderedOutput('Not pending');
142
+ });
143
+});