|
10 | 10 |
|
11 | 11 | 'use strict';
|
12 | 12 |
|
| 13 | +function normalizeCodeLocInfo(str) { |
| 14 | + return ( |
| 15 | + str && |
| 16 | + str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) { |
| 17 | + return '\n in ' + name + (/\d/.test(m) ? ' (at **)' : ''); |
| 18 | + }) |
| 19 | + ); |
| 20 | +} |
| 21 | + |
13 | 22 | const heldValues = [];
|
14 | 23 | let finalizationCallback;
|
15 | 24 | function FinalizationRegistryMock(callback) {
|
@@ -69,6 +78,14 @@ describe('ReactFlight', () => {
|
69 | 78 | error,
|
70 | 79 | };
|
71 | 80 | }
|
| 81 | + componentDidCatch(error, errorInfo) { |
| 82 | + expect(error).toBe(this.state.error); |
| 83 | + if (this.props.expectedStack !== undefined) { |
| 84 | + expect(normalizeCodeLocInfo(errorInfo.componentStack)).toBe( |
| 85 | + this.props.expectedStack, |
| 86 | + ); |
| 87 | + } |
| 88 | + } |
72 | 89 | componentDidMount() {
|
73 | 90 | expect(this.state.hasError).toBe(true);
|
74 | 91 | expect(this.state.error).toBeTruthy();
|
@@ -900,6 +917,96 @@ describe('ReactFlight', () => {
|
900 | 917 | });
|
901 | 918 | });
|
902 | 919 |
|
| 920 | + it('should include server components in error boundary stacks in dev', async () => { |
| 921 | + const ClientErrorBoundary = clientReference(ErrorBoundary); |
| 922 | + |
| 923 | + function Throw({value}) { |
| 924 | + throw value; |
| 925 | + } |
| 926 | + |
| 927 | + const expectedStack = __DEV__ |
| 928 | + ? // TODO: This should include Throw but it doesn't have a Fiber. |
| 929 | + '\n in div' + '\n in ErrorBoundary (at **)' + '\n in App' |
| 930 | + : '\n in div' + '\n in ErrorBoundary (at **)'; |
| 931 | + |
| 932 | + function App() { |
| 933 | + return ( |
| 934 | + <ClientErrorBoundary |
| 935 | + expectedMessage="This is a real Error." |
| 936 | + expectedStack={expectedStack}> |
| 937 | + <div> |
| 938 | + <Throw value={new TypeError('This is a real Error.')} /> |
| 939 | + </div> |
| 940 | + </ClientErrorBoundary> |
| 941 | + ); |
| 942 | + } |
| 943 | + |
| 944 | + const transport = ReactNoopFlightServer.render(<App />, { |
| 945 | + onError(x) { |
| 946 | + if (__DEV__) { |
| 947 | + return 'a dev digest'; |
| 948 | + } |
| 949 | + if (x instanceof Error) { |
| 950 | + return `digest("${x.message}")`; |
| 951 | + } else if (Array.isArray(x)) { |
| 952 | + return `digest([])`; |
| 953 | + } else if (typeof x === 'object' && x !== null) { |
| 954 | + return `digest({})`; |
| 955 | + } |
| 956 | + return `digest(${String(x)})`; |
| 957 | + }, |
| 958 | + }); |
| 959 | + |
| 960 | + await act(() => { |
| 961 | + startTransition(() => { |
| 962 | + ReactNoop.render(ReactNoopFlightClient.read(transport)); |
| 963 | + }); |
| 964 | + }); |
| 965 | + }); |
| 966 | + |
| 967 | + it('should include server components in warning stacks', async () => { |
| 968 | + function Component() { |
| 969 | + // Trigger key warning |
| 970 | + return <div>{[<span />]}</div>; |
| 971 | + } |
| 972 | + const ClientComponent = clientReference(Component); |
| 973 | + |
| 974 | + function Indirection({children}) { |
| 975 | + return children; |
| 976 | + } |
| 977 | + |
| 978 | + const expectedStack = __DEV__ |
| 979 | + ? // TODO: This should include Throw but it doesn't have a Fiber. |
| 980 | + '\n in div' + '\n in ErrorBoundary (at **)' + '\n in App' |
| 981 | + : '\n in div' + '\n in ErrorBoundary (at **)'; |
| 982 | + |
| 983 | + function App() { |
| 984 | + return ( |
| 985 | + <Indirection> |
| 986 | + <ClientComponent /> |
| 987 | + </Indirection> |
| 988 | + ); |
| 989 | + } |
| 990 | + |
| 991 | + const transport = ReactNoopFlightServer.render(<App />); |
| 992 | + |
| 993 | + await expect(async () => { |
| 994 | + await act(() => { |
| 995 | + startTransition(() => { |
| 996 | + ReactNoop.render(ReactNoopFlightClient.read(transport)); |
| 997 | + }); |
| 998 | + }); |
| 999 | + }).toErrorDev( |
| 1000 | + 'Each child in a list should have a unique "key" prop.\n' + |
| 1001 | + '\n' + |
| 1002 | + 'Check the render method of `Component`. See https://reactjs.org/link/warning-keys for more information.\n' + |
| 1003 | + ' in span (at **)\n' + |
| 1004 | + ' in Component (at **)\n' + |
| 1005 | + ' in Indirection (at **)\n' + |
| 1006 | + ' in App (at **)', |
| 1007 | + ); |
| 1008 | + }); |
| 1009 | + |
903 | 1010 | it('should trigger the inner most error boundary inside a Client Component', async () => {
|
904 | 1011 | function ServerComponent() {
|
905 | 1012 | throw new Error('This was thrown in the Server Component.');
|
|
0 commit comments