|
12 | 12 |
|
13 | 13 | import {patchSetImmediate} from '../../../../scripts/jest/patchSetImmediate'; |
14 | 14 |
|
| 15 | +const path = require('path'); |
| 16 | + |
15 | 17 | global.ReadableStream = |
16 | 18 | require('web-streams-polyfill/ponyfill/es6').ReadableStream; |
17 | 19 |
|
@@ -88,12 +90,25 @@ describe('ReactFlightDOMNode', () => { |
88 | 90 | ); |
89 | 91 | } |
90 | 92 |
|
91 | | - function normalizeCodeLocInfo(str) { |
| 93 | + const repoRoot = path.resolve(__dirname, '../../../../'); |
| 94 | + |
| 95 | + function normalizeCodeLocInfo(str, {preserveLocation = false} = {}) { |
92 | 96 | return ( |
93 | 97 | str && |
94 | | - str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) { |
95 | | - return ' in ' + name + (/\d/.test(m) ? ' (at **)' : ''); |
96 | | - }) |
| 98 | + str.replace( |
| 99 | + /^ +(?:at|in) ([\S]+) ([^\n]*)/gm, |
| 100 | + function (m, name, location) { |
| 101 | + return ( |
| 102 | + ' in ' + |
| 103 | + name + |
| 104 | + (/\d/.test(m) |
| 105 | + ? preserveLocation |
| 106 | + ? ' ' + location.replace(repoRoot, '') |
| 107 | + : ' (at **)' |
| 108 | + : '') |
| 109 | + ); |
| 110 | + }, |
| 111 | + ) |
97 | 112 | ); |
98 | 113 | } |
99 | 114 |
|
@@ -896,6 +911,151 @@ describe('ReactFlightDOMNode', () => { |
896 | 911 | } |
897 | 912 | }); |
898 | 913 |
|
| 914 | + // @gate enableHalt && enableAsyncDebugInfo |
| 915 | + it('includes deeper location for aborted hanging promises', async () => { |
| 916 | + const serverRenderAbortController = new AbortController(); |
| 917 | + const serverCleanupAbortController = new AbortController(); |
| 918 | + |
| 919 | + serverRenderAbortController.signal.addEventListener('abort', () => { |
| 920 | + serverCleanupAbortController.abort(); |
| 921 | + }); |
| 922 | + |
| 923 | + function createHangingPromise(signal) { |
| 924 | + return new Promise((resolve, reject) => { |
| 925 | + signal.addEventListener('abort', () => reject(signal.reason)); |
| 926 | + }); |
| 927 | + } |
| 928 | + |
| 929 | + async function Component({promise}) { |
| 930 | + await promise; |
| 931 | + return null; |
| 932 | + } |
| 933 | + |
| 934 | + const promise = createHangingPromise(serverCleanupAbortController.signal); |
| 935 | + |
| 936 | + function App() { |
| 937 | + return ReactServer.createElement( |
| 938 | + 'html', |
| 939 | + null, |
| 940 | + ReactServer.createElement( |
| 941 | + 'body', |
| 942 | + null, |
| 943 | + ReactServer.createElement( |
| 944 | + ReactServer.Suspense, |
| 945 | + {fallback: 'Loading...'}, |
| 946 | + ReactServer.createElement(Component, {promise}), |
| 947 | + ), |
| 948 | + ), |
| 949 | + ); |
| 950 | + } |
| 951 | + |
| 952 | + const errors = []; |
| 953 | + |
| 954 | + const {pendingResult} = await serverAct(async () => { |
| 955 | + // destructure trick to avoid the act scope from awaiting the returned value |
| 956 | + return { |
| 957 | + pendingResult: ReactServerDOMStaticServer.unstable_prerender( |
| 958 | + ReactServer.createElement(App, null), |
| 959 | + webpackMap, |
| 960 | + { |
| 961 | + signal: serverRenderAbortController.signal, |
| 962 | + onError(error) { |
| 963 | + errors.push(error); |
| 964 | + }, |
| 965 | + filterStackFrame, |
| 966 | + }, |
| 967 | + ), |
| 968 | + }; |
| 969 | + }); |
| 970 | + |
| 971 | + await serverAct( |
| 972 | + () => |
| 973 | + new Promise(resolve => { |
| 974 | + setImmediate(() => { |
| 975 | + serverRenderAbortController.abort(); |
| 976 | + resolve(); |
| 977 | + }); |
| 978 | + }), |
| 979 | + ); |
| 980 | + |
| 981 | + const {prelude} = await pendingResult; |
| 982 | + |
| 983 | + expect(errors).toEqual([]); |
| 984 | + |
| 985 | + function ClientRoot({response}) { |
| 986 | + return use(response); |
| 987 | + } |
| 988 | + |
| 989 | + const prerenderResponse = ReactServerDOMClient.createFromReadableStream( |
| 990 | + await createBufferedUnclosingStream(prelude), |
| 991 | + { |
| 992 | + serverConsumerManifest: { |
| 993 | + moduleMap: null, |
| 994 | + moduleLoading: null, |
| 995 | + }, |
| 996 | + }, |
| 997 | + ); |
| 998 | + |
| 999 | + let componentStack; |
| 1000 | + let ownerStack; |
| 1001 | + |
| 1002 | + const clientAbortController = new AbortController(); |
| 1003 | + |
| 1004 | + const fizzPrerenderStreamResult = ReactDOMFizzStatic.prerender( |
| 1005 | + React.createElement(ClientRoot, {response: prerenderResponse}), |
| 1006 | + { |
| 1007 | + signal: clientAbortController.signal, |
| 1008 | + onError(error, errorInfo) { |
| 1009 | + componentStack = errorInfo.componentStack; |
| 1010 | + ownerStack = React.captureOwnerStack |
| 1011 | + ? React.captureOwnerStack() |
| 1012 | + : null; |
| 1013 | + }, |
| 1014 | + }, |
| 1015 | + ); |
| 1016 | + |
| 1017 | + await await serverAct( |
| 1018 | + async () => |
| 1019 | + new Promise(resolve => { |
| 1020 | + setImmediate(() => { |
| 1021 | + clientAbortController.abort(); |
| 1022 | + resolve(); |
| 1023 | + }); |
| 1024 | + }), |
| 1025 | + ); |
| 1026 | + |
| 1027 | + const fizzPrerenderStream = await fizzPrerenderStreamResult; |
| 1028 | + const prerenderHTML = await readWebResult(fizzPrerenderStream.prelude); |
| 1029 | + |
| 1030 | + expect(prerenderHTML).toContain('Loading...'); |
| 1031 | + |
| 1032 | + if (__DEV__) { |
| 1033 | + expect(normalizeCodeLocInfo(componentStack, {preserveLocation: true})) |
| 1034 | + .toMatchInlineSnapshot(` |
| 1035 | + " |
| 1036 | + in Component (file:///packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js:930:7) |
| 1037 | + in Suspense |
| 1038 | + in body |
| 1039 | + in html |
| 1040 | + in App (file:///packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js:946:25) |
| 1041 | + in ClientRoot (/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js:985:54)" |
| 1042 | + `); |
| 1043 | + } else { |
| 1044 | + expect(normalizeCodeLocInfo(componentStack)).toMatchInlineSnapshot(``); |
| 1045 | + } |
| 1046 | + |
| 1047 | + if (__DEV__) { |
| 1048 | + expect(normalizeCodeLocInfo(ownerStack, {preserveLocation: true})) |
| 1049 | + .toMatchInlineSnapshot(` |
| 1050 | + " |
| 1051 | + in Component (file:///packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js:930:7) |
| 1052 | + in App (file:///packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js:946:25)" |
| 1053 | + `); |
| 1054 | + } else { |
| 1055 | + expect(ownerStack).toBeNull(); |
| 1056 | + } |
| 1057 | + }); |
| 1058 | + |
899 | 1059 | // @gate experimental |
900 | 1060 | // @gate enableHalt |
901 | 1061 | it('can handle an empty prelude when prerendering', async () => { |
|
0 commit comments