Add Concurrent Mode test
Dan Abramov committed
Apr 19, 2019 at 01:18 UTC
72e67b343f2196c572d59e4ea95f8d7e3903647e
2 files changed
+685
src/__tests__/__snapshots__/storeStressTestConcurrent-test.js.snap
+254
@@ -115,6 +115,260 @@ exports[`StoreStressConcurrent should handle a stress test for Suspense (Concurr
115
<Y>
116
`;
117
118
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 1`] = `
119
+[root]
120
+ ▾ <Root>
121
+ <X>
122
+ ▾ <Suspense>
123
+ ▾ <MaybeSuspend>
124
+ <A key="a">
125
+ <Z>
126
+ <Y>
127
+`;
128
+
129
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 2`] = `
130
+[root]
131
+ ▾ <Root>
132
+ <X>
133
+ ▾ <Suspense>
134
+ ▾ <MaybeSuspend>
135
+ <A key="a">
136
+ <Z>
137
+ <Y>
138
+`;
139
+
140
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 3`] = `
141
+[root]
142
+ ▾ <Root>
143
+ <X>
144
+ ▾ <Suspense>
145
+ ▾ <MaybeSuspend>
146
+ <A key="a">
147
+ <B key="b">
148
+ <C key="c">
149
+ <Z>
150
+ <Y>
151
+`;
152
+
153
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 4`] = `
154
+[root]
155
+ ▾ <Root>
156
+ <X>
157
+ ▾ <Suspense>
158
+ ▾ <MaybeSuspend>
159
+ <C key="c">
160
+ <B key="b">
161
+ <A key="a">
162
+ <Z>
163
+ <Y>
164
+`;
165
+
166
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 5`] = `
167
+[root]
168
+ ▾ <Root>
169
+ <X>
170
+ ▾ <Suspense>
171
+ ▾ <MaybeSuspend>
172
+ <C key="c">
173
+ <A key="a">
174
+ <Z>
175
+ <Y>
176
+`;
177
+
178
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 6`] = `
179
+[root]
180
+ ▾ <Root>
181
+ <X>
182
+ ▾ <Suspense>
183
+ ▾ <MaybeSuspend>
184
+ <C key="c">
185
+ <A key="a">
186
+ <Z>
187
+ <Y>
188
+`;
189
+
190
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 7`] = `
191
+[root]
192
+ ▾ <Root>
193
+ <X>
194
+ ▾ <Suspense>
195
+ ▾ <MaybeSuspend>
196
+ <C key="c">
197
+ <A key="a">
198
+ <Z>
199
+ <Y>
200
+`;
201
+
202
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 8`] = `
203
+[root]
204
+ ▾ <Root>
205
+ <X>
206
+ ▾ <Suspense>
207
+ ▾ <MaybeSuspend>
208
+ <A key="a">
209
+ <B key="b">
210
+ <Z>
211
+ <Y>
212
+`;
213
+
214
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 9`] = `
215
+[root]
216
+ ▾ <Root>
217
+ <X>
218
+ ▾ <Suspense>
219
+ ▾ <MaybeSuspend>
220
+ <A key="a">
221
+ <Z>
222
+ <Y>
223
+`;
224
+
225
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 10`] = `
226
+[root]
227
+ ▾ <Root>
228
+ <X>
229
+ ▾ <Suspense>
230
+ ▾ <MaybeSuspend>
231
+ <Z>
232
+ <Y>
233
+`;
234
+
235
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 11`] = `
236
+[root]
237
+ ▾ <Root>
238
+ <X>
239
+ ▾ <Suspense>
240
+ ▾ <MaybeSuspend>
241
+ <B key="b">
242
+ <Z>
243
+ <Y>
244
+`;
245
+
246
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 12`] = `
247
+[root]
248
+ ▾ <Root>
249
+ <X>
250
+ ▾ <Suspense>
251
+ ▾ <MaybeSuspend>
252
+ <A key="a">
253
+ <Z>
254
+ <Y>
255
+`;
256
+
257
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 13`] = `
258
+[root]
259
+ ▾ <Root>
260
+ <X>
261
+ ▾ <Suspense>
262
+ <A key="a">
263
+ <Y>
264
+`;
265
+
266
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 14`] = `
267
+[root]
268
+ ▾ <Root>
269
+ <X>
270
+ ▾ <Suspense>
271
+ <A key="a">
272
+ <Y>
273
+`;
274
+
275
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 15`] = `
276
+[root]
277
+ ▾ <Root>
278
+ <X>
279
+ ▾ <Suspense>
280
+ <A key="a">
281
+ <B key="b">
282
+ <C key="c">
283
+ <Y>
284
+`;
285
+
286
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 16`] = `
287
+[root]
288
+ ▾ <Root>
289
+ <X>
290
+ ▾ <Suspense>
291
+ <C key="c">
292
+ <B key="b">
293
+ <A key="a">
294
+ <Y>
295
+`;
296
+
297
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 17`] = `
298
+[root]
299
+ ▾ <Root>
300
+ <X>
301
+ ▾ <Suspense>
302
+ <C key="c">
303
+ <A key="a">
304
+ <Y>
305
+`;
306
+
307
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 18`] = `
308
+[root]
309
+ ▾ <Root>
310
+ <X>
311
+ ▾ <Suspense>
312
+ <C key="c">
313
+ <A key="a">
314
+ <Y>
315
+`;
316
+
317
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 19`] = `
318
+[root]
319
+ ▾ <Root>
320
+ <X>
321
+ ▾ <Suspense>
322
+ <C key="c">
323
+ <A key="a">
324
+ <Y>
325
+`;
326
+
327
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 20`] = `
328
+[root]
329
+ ▾ <Root>
330
+ <X>
331
+ ▾ <Suspense>
332
+ <A key="a">
333
+ <B key="b">
334
+ <Y>
335
+`;
336
+
337
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 21`] = `
338
+[root]
339
+ ▾ <Root>
340
+ <X>
341
+ ▾ <Suspense>
342
+ <A key="a">
343
+ <Y>
344
+`;
345
+
346
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 22`] = `
347
+[root]
348
+ ▾ <Root>
349
+ <X>
350
+ <Suspense>
351
+ <Y>
352
+`;
353
+
354
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 23`] = `
355
+[root]
356
+ ▾ <Root>
357
+ <X>
358
+ ▾ <Suspense>
359
+ <B key="b">
360
+ <Y>
361
+`;
362
+
363
+exports[`StoreStressConcurrent should handle a stress test for Suspense without type change (Concurrent Mode) 24`] = `
364
+[root]
365
+ ▾ <Root>
366
+ <X>
367
+ ▾ <Suspense>
368
+ <A key="a">
369
+ <Y>
370
+`;
371
+
372
exports[`StoreStressConcurrent should handle a stress test with different tree operations (Concurrent Mode): 1: abcde 1`] = `
373
[root]
374
▾ <Parent>
src/__tests__/storeStressTestConcurrent-test.js
+431
@@ -668,4 +668,435 @@ describe('StoreStressConcurrent', () => {
668
}
669
}
670
});
671
+
672
+ it('should handle a stress test for Suspense without type change (Concurrent Mode)', () => {
673
+ const A = () => 'a';
674
+ const B = () => 'b';
675
+ const C = () => 'c';
676
+ const X = () => 'x';
677
+ const Y = () => 'y';
678
+ const Z = () => 'z';
679
+ const a = <A key="a" />;
680
+ const b = <B key="b" />;
681
+ const c = <C key="c" />;
682
+ const z = <Z key="z" />;
683
+
684
+ // prettier-ignore
685
+ const steps = [
686
+ a,
687
+ [a],
688
+ [a, b, c],
689
+ [c, b, a],
690
+ [c, null, a],
691
+ <React.Fragment>{c}{a}</React.Fragment>,
692
+ <div>{c}{a}</div>,
693
+ <div><span>{a}</span>{b}</div>,
694
+ [[a]],
695
+ null,
696
+ b,
697
+ a
698
+ ];
699
+
700
+ const Never = () => {
701
+ throw new Promise(() => {});
702
+ };
703
+
704
+ const MaybeSuspend = ({ children, suspend }) => {
705
+ if (suspend) {
706
+ return (
707
+ <div>
708
+ {children}
709
+ <Never />
710
+ <X />
711
+ </div>
712
+ );
713
+ }
714
+ return (
715
+ <div>
716
+ {children}
717
+ <Z />
718
+ </div>
719
+ );
720
+ };
721
+
722
+ const Root = ({ children }) => {
723
+ return children;
724
+ };
725
+
726
+ // 1. For each step, check Suspense can render them as initial primary content.
727
+ // This is the only step where we use Jest snapshots.
728
+ let snapshots = [];
729
+ let container = document.createElement('div');
730
+ // $FlowFixMe
731
+ let root = ReactDOM.unstable_createRoot(container);
732
+ for (let i = 0; i < steps.length; i++) {
733
+ act(() =>
734
+ root.render(
735
+ <Root>
736
+ <X />
737
+ <React.Suspense fallback={z}>
738
+ <MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
739
+ </React.Suspense>
740
+ <Y />
741
+ </Root>
742
+ )
743
+ );
744
+ // We snapshot each step once so it doesn't regress.
745
+ expect(store).toMatchSnapshot();
746
+ snapshots.push(print(store));
747
+ act(() => root.unmount());
748
+ expect(print(store)).toBe('');
749
+ }
750
+
751
+ // 2. Verify check Suspense can render same steps as initial fallback content.
752
+ // We don't actually assert here because the tree includes <MaybeSuspend>
753
+ // which is different from the snapshots above. So we take more snapshots.
754
+ let fallbackSnapshots = [];
755
+ for (let i = 0; i < steps.length; i++) {
756
+ act(() =>
757
+ root.render(
758
+ <Root>
759
+ <X />
760
+ <React.Suspense fallback={steps[i]}>
761
+ <Z />
762
+ <MaybeSuspend suspend={true}>{steps[i]}</MaybeSuspend>
763
+ <Z />
764
+ </React.Suspense>
765
+ <Y />
766
+ </Root>
767
+ )
768
+ );
769
+ // We snapshot each step once so it doesn't regress.
770
+ expect(store).toMatchSnapshot();
771
+ fallbackSnapshots.push(print(store));
772
+ act(() => root.unmount());
773
+ expect(print(store)).toBe('');
774
+ }
775
+
776
+ // 3. Verify we can update from each step to each step in primary mode.
777
+ for (let i = 0; i < steps.length; i++) {
778
+ for (let j = 0; j < steps.length; j++) {
779
+ // Always start with a fresh container and steps[i].
780
+ container = document.createElement('div');
781
+ // $FlowFixMe
782
+ root = ReactDOM.unstable_createRoot(container);
783
+ act(() =>
784
+ root.render(
785
+ <Root>
786
+ <X />
787
+ <React.Suspense fallback={z}>
788
+ <MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
789
+ </React.Suspense>
790
+ <Y />
791
+ </Root>
792
+ )
793
+ );
794
+ expect(print(store)).toEqual(snapshots[i]);
795
+ // Re-render with steps[j].
796
+ act(() =>
797
+ root.render(
798
+ <Root>
799
+ <X />
800
+ <React.Suspense fallback={z}>
801
+ <MaybeSuspend suspend={false}>{steps[j]}</MaybeSuspend>
802
+ </React.Suspense>
803
+ <Y />
804
+ </Root>
805
+ )
806
+ );
807
+ // Verify the successful transition to steps[j].
808
+ expect(print(store)).toEqual(snapshots[j]);
809
+ // Check that we can transition back again.
810
+ act(() =>
811
+ root.render(
812
+ <Root>
813
+ <X />
814
+ <React.Suspense fallback={z}>
815
+ <MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
816
+ </React.Suspense>
817
+ <Y />
818
+ </Root>
819
+ )
820
+ );
821
+ expect(print(store)).toEqual(snapshots[i]);
822
+ // Clean up after every iteration.
823
+ act(() => root.unmount());
824
+ expect(print(store)).toBe('');
825
+ }
826
+ }
827
+
828
+ // 4. Verify we can update from each step to each step in fallback mode.
829
+ for (let i = 0; i < steps.length; i++) {
830
+ for (let j = 0; j < steps.length; j++) {
831
+ // Always start with a fresh container and steps[i].
832
+ container = document.createElement('div');
833
+ // $FlowFixMe
834
+ root = ReactDOM.unstable_createRoot(container);
835
+ act(() =>
836
+ root.render(
837
+ <Root>
838
+ <X />
839
+ <React.Suspense fallback={steps[i]}>
840
+ <Z />
841
+ <MaybeSuspend suspend={true}>
842
+ <X />
843
+ <Y />
844
+ </MaybeSuspend>
845
+ <Z />
846
+ </React.Suspense>
847
+ <Y />
848
+ </Root>
849
+ )
850
+ );
851
+ expect(print(store)).toEqual(fallbackSnapshots[i]);
852
+ // Re-render with steps[j].
853
+ act(() =>
854
+ root.render(
855
+ <Root>
856
+ <X />
857
+ <React.Suspense fallback={steps[j]}>
858
+ <Z />
859
+ <MaybeSuspend suspend={true}>
860
+ <Y />
861
+ <X />
862
+ </MaybeSuspend>
863
+ <Z />
864
+ </React.Suspense>
865
+ <Y />
866
+ </Root>
867
+ )
868
+ );
869
+ // Verify the successful transition to steps[j].
870
+ expect(print(store)).toEqual(fallbackSnapshots[j]);
871
+ // Check that we can transition back again.
872
+ act(() =>
873
+ root.render(
874
+ <Root>
875
+ <X />
876
+ <React.Suspense fallback={steps[i]}>
877
+ <Z />
878
+ <MaybeSuspend suspend={true}>
879
+ <X />
880
+ <Y />
881
+ </MaybeSuspend>
882
+ <Z />
883
+ </React.Suspense>
884
+ <Y />
885
+ </Root>
886
+ )
887
+ );
888
+ expect(print(store)).toEqual(fallbackSnapshots[i]);
889
+ // Clean up after every iteration.
890
+ act(() => root.unmount());
891
+ expect(print(store)).toBe('');
892
+ }
893
+ }
894
+
895
+ // 5. Verify we can update from each step to each step when moving primary -> fallback.
896
+ for (let i = 0; i < steps.length; i++) {
897
+ for (let j = 0; j < steps.length; j++) {
898
+ // Always start with a fresh container and steps[i].
899
+ container = document.createElement('div');
900
+ // $FlowFixMe
901
+ root = ReactDOM.unstable_createRoot(container);
902
+ act(() =>
903
+ root.render(
904
+ <Root>
905
+ <X />
906
+ <React.Suspense fallback={z}>
907
+ <MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
908
+ </React.Suspense>
909
+ <Y />
910
+ </Root>
911
+ )
912
+ );
913
+ expect(print(store)).toEqual(snapshots[i]);
914
+ // Re-render with steps[j].
915
+ act(() =>
916
+ root.render(
917
+ <Root>
918
+ <X />
919
+ <React.Suspense fallback={steps[j]}>
920
+ <MaybeSuspend suspend={true}>{steps[i]}</MaybeSuspend>
921
+ </React.Suspense>
922
+ <Y />
923
+ </Root>
924
+ )
925
+ );
926
+ // Verify the successful transition to steps[j].
927
+ expect(print(store)).toEqual(fallbackSnapshots[j]);
928
+ // Check that we can transition back again.
929
+ act(() =>
930
+ root.render(
931
+ <Root>
932
+ <X />
933
+ <React.Suspense fallback={z}>
934
+ <MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
935
+ </React.Suspense>
936
+ <Y />
937
+ </Root>
938
+ )
939
+ );
940
+ expect(print(store)).toEqual(snapshots[i]);
941
+ // Clean up after every iteration.
942
+ act(() => root.unmount());
943
+ expect(print(store)).toBe('');
944
+ }
945
+ }
946
+
947
+ // 6. Verify we can update from each step to each step when moving fallback -> primary.
948
+ for (let i = 0; i < steps.length; i++) {
949
+ for (let j = 0; j < steps.length; j++) {
950
+ // Always start with a fresh container and steps[i].
951
+ container = document.createElement('div');
952
+ // $FlowFixMe
953
+ root = ReactDOM.unstable_createRoot(container);
954
+ act(() =>
955
+ root.render(
956
+ <Root>
957
+ <X />
958
+ <React.Suspense fallback={steps[i]}>
959
+ <MaybeSuspend suspend={true}>{steps[j]}</MaybeSuspend>
960
+ </React.Suspense>
961
+ <Y />
962
+ </Root>
963
+ )
964
+ );
965
+ expect(print(store)).toEqual(fallbackSnapshots[i]);
966
+ // Re-render with steps[j].
967
+ act(() =>
968
+ root.render(
969
+ <Root>
970
+ <X />
971
+ <React.Suspense fallback={steps[i]}>
972
+ <MaybeSuspend suspend={false}>{steps[j]}</MaybeSuspend>
973
+ </React.Suspense>
974
+ <Y />
975
+ </Root>
976
+ )
977
+ );
978
+ // Verify the successful transition to steps[j].
979
+ expect(print(store)).toEqual(snapshots[j]);
980
+ // Check that we can transition back again.
981
+ act(() =>
982
+ root.render(
983
+ <Root>
984
+ <X />
985
+ <React.Suspense fallback={steps[i]}>
986
+ <MaybeSuspend suspend={true}>{steps[j]}</MaybeSuspend>
987
+ </React.Suspense>
988
+ <Y />
989
+ </Root>
990
+ )
991
+ );
992
+ expect(print(store)).toEqual(fallbackSnapshots[i]);
993
+ // Clean up after every iteration.
994
+ act(() => root.unmount());
995
+ expect(print(store)).toBe('');
996
+ }
997
+ }
998
+
999
+ // 7. Verify we can update from each step to each step when toggling Suspense.
1000
+ for (let i = 0; i < steps.length; i++) {
1001
+ for (let j = 0; j < steps.length; j++) {
1002
+ // Always start with a fresh container and steps[i].
1003
+ container = document.createElement('div');
1004
+ // $FlowFixMe
1005
+ root = ReactDOM.unstable_createRoot(container);
1006
+ act(() =>
1007
+ root.render(
1008
+ <Root>
1009
+ <X />
1010
+ <React.Suspense fallback={steps[j]}>
1011
+ <MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
1012
+ </React.Suspense>
1013
+ <Y />
1014
+ </Root>
1015
+ )
1016
+ );
1017
+
1018
+ // We get ID from the index in the tree above:
1019
+ // Root, X, Suspense, ...
1020
+ // ^ (index is 2)
1021
+ const suspenseID = store.getElementIDAtIndex(2);
1022
+
1023
+ // Force fallback.
1024
+ expect(print(store)).toEqual(snapshots[i]);
1025
+ act(() => {
1026
+ const suspenseID = store.getElementIDAtIndex(2);
1027
+ bridge.send('overrideSuspense', {
1028
+ id: suspenseID,
1029
+ rendererID: store.getRendererIDForElement(suspenseID),
1030
+ forceFallback: true,
1031
+ });
1032
+ });
1033
+ expect(print(store)).toEqual(fallbackSnapshots[j]);
1034
+
1035
+ // Stop forcing fallback.
1036
+ act(() => {
1037
+ bridge.send('overrideSuspense', {
1038
+ id: suspenseID,
1039
+ rendererID: store.getRendererIDForElement(suspenseID),
1040
+ forceFallback: false,
1041
+ });
1042
+ });
1043
+ expect(print(store)).toEqual(snapshots[i]);
1044
+
1045
+ // Trigger actual fallback.
1046
+ act(() =>
1047
+ root.render(
1048
+ <Root>
1049
+ <X />
1050
+ <React.Suspense fallback={steps[j]}>
1051
+ <MaybeSuspend suspend={true}>{steps[i]}</MaybeSuspend>
1052
+ </React.Suspense>
1053
+ <Y />
1054
+ </Root>
1055
+ )
1056
+ );
1057
+ expect(print(store)).toEqual(fallbackSnapshots[j]);
1058
+
1059
+ // Force fallback while we're in fallback mode.
1060
+ act(() => {
1061
+ bridge.send('overrideSuspense', {
1062
+ id: suspenseID,
1063
+ rendererID: store.getRendererIDForElement(suspenseID),
1064
+ forceFallback: true,
1065
+ });
1066
+ });
1067
+ // Keep seeing fallback content.
1068
+ expect(print(store)).toEqual(fallbackSnapshots[j]);
1069
+
1070
+ // Switch to primary mode.
1071
+ act(() =>
1072
+ root.render(
1073
+ <Root>
1074
+ <X />
1075
+ <React.Suspense fallback={steps[j]}>
1076
+ <MaybeSuspend suspend={false}>{steps[i]}</MaybeSuspend>
1077
+ </React.Suspense>
1078
+ <Y />
1079
+ </Root>
1080
+ )
1081
+ );
1082
+ // Fallback is still forced though.
1083
+ expect(print(store)).toEqual(fallbackSnapshots[j]);
1084
+
1085
+ // Stop forcing fallback. This reverts to primary content.
1086
+ act(() => {
1087
+ bridge.send('overrideSuspense', {
1088
+ id: suspenseID,
1089
+ rendererID: store.getRendererIDForElement(suspenseID),
1090
+ forceFallback: false,
1091
+ });
1092
+ });
1093
+ // Now we see primary content.
1094
+ expect(print(store)).toEqual(snapshots[i]);
1095
+
1096
+ // Clean up after every iteration.
1097
+ act(() => root.unmount());
1098
+ expect(print(store)).toBe('');
1099
+ }
1100
+ }
1101
+ });
1102
});