3003
</>,
3004
);
3005
});
3006
+
3007
+ // @gate enableSuspenseList
3008
+ it(
3009
+ 'regression test: SuspenseList should never force boundaries deeper than ' +
3010
+ 'a single level into fallback mode',
3011
+ async () => {
3012
+ const A = createAsyncText('A');
3013
+
3014
+ function UnreachableFallback() {
3015
+ throw new Error('Should never be thrown!');
3016
+ }
3017
+
3018
+ function Repro({update}) {
3019
+ return (
3020
+ <SuspenseList revealOrder="forwards">
3021
+ {update && (
3022
+ <Suspense fallback={<Text text="Loading A..." />}>
3023
+ <A />
3024
+ </Suspense>
3025
+ )}
3026
+ <Suspense fallback={<Text text="Loading B..." />}>
3027
+ {update ? (
3028
+ <Suspense fallback={<UnreachableFallback />}>
3029
+ <Text text="B2" />
3030
+ </Suspense>
3031
+ ) : (
3032
+ <Text text="B1" />
3033
+ )}
3034
+ </Suspense>
3035
+ <Suspense fallback={<Text text="Loading C..." />}>
3036
+ <Text text="C" />
3037
+ </Suspense>
3038
+ {update && (
3039
+ <Suspense fallback={<Text text="Loading D..." />}>
3040
+ <Text text="D" />
3041
+ </Suspense>
3042
+ )}
3043
+ </SuspenseList>
3044
+ );
3045
+ }
3046
+
3047
+ // Initial mount. Only two rows are mounted, B and C.
3048
+ const root = ReactNoop.createRoot();
3049
+ await act(() => root.render(<Repro update={false} />));
3050
+ assertLog(['B1', 'C']);
3051
+ expect(root).toMatchRenderedOutput(
3052
+ <>
3053
+ <span>B1</span>
3054
+ <span>C</span>
3055
+ </>,
3056
+ );
3057
+
3058
+ // During the update, a few things happen simultaneously:
3059
+ // - A new row, A, is inserted into the head. This row suspends.
3060
+ // - The context in row B is replaced. The new content contains a nested
3061
+ // Suspense boundary.
3062
+ // - A new row, D, is inserted into the tail.
3063
+ await act(() => root.render(<Repro update={true} />));
3064
+ assertLog([
3065
+ // During the first pass, the new row, A, suspends. This means any new
3066
+ // rows in the tail should be forced into fallback mode.
3067
+ 'Suspend! [A]',
3068
+ 'Loading A...',
3069
+ 'B2',
3070
+ 'C',
3071
+
3072
+ // A second pass is used to render the fallbacks in the tail.
3073
+ //
3074
+ // Rows B and C were already mounted, so they should not be forced into
3075
+ // fallback mode.
3076
+ //
3077
+ // In the regression that this test was written for, the inner
3078
+ // Suspense boundary around B2 was incorrectly activated. Only the
3079
+ // nearest fallbacks per row should be activated, and only if they
3080
+ // haven't already mounted.
3081
+ 'Loading A...',
3082
+ 'B2',
3083
+ 'C',
3084
+
3085
+ // D is part of the tail, so it should show a fallback.
3086
+ 'Loading D...',
3087
+ ]);
3088
+ expect(root).toMatchRenderedOutput(
3089
+ <>
3090
+ <span>Loading A...</span>
3091
+ <span>B2</span>
3092
+ <span>C</span>
3093
+ <span>Loading D...</span>
3094
+ </>,
3095
+ );
3096
+
3097
+ // Now finish loading A.
3098
+ await act(() => A.resolve());
3099
+ assertLog(['A', 'D']);
3100
+ expect(root).toMatchRenderedOutput(
3101
+ <>
3102
+ <span>A</span>
3103
+ <span>B2</span>
3104
+ <span>C</span>
3105
+ <span>D</span>
3106
+ </>,
3107
+ );
3108
+ },
3109
+ );
3110
});