Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2876,7 +2876,9 @@ describe('ReactFlightDOM', () => {
};
});

controller.abort('boom');
await serverAct(() => {
controller.abort('boom');
});
resolveGreeting();
const {prelude} = await pendingResult;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2549,7 +2549,7 @@ describe('ReactFlightDOMBrowser', () => {

controller.abort('boom');
resolveGreeting();
const {prelude} = await pendingResult;
const {prelude} = await serverAct(() => pendingResult);
expect(errors).toEqual([]);

function ClientRoot({response}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,9 @@ describe('ReactFlightDOMEdge', () => {
};
});

controller.abort('boom');
await serverAct(() => {
controller.abort('boom');
});
resolveGreeting();
const {prelude} = await pendingResult;

Expand Down Expand Up @@ -1497,7 +1499,7 @@ describe('ReactFlightDOMEdge', () => {
});

controller.abort();
const {prelude} = await pendingResult;
const {prelude} = await serverAct(() => pendingResult);

expect(errors).toEqual([]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ describe('ReactFlightDOMNode', () => {
use = React.use;
});

function filterStackFrame(filename, functionName) {
return (
filename !== '' &&
!filename.startsWith('node:') &&
!filename.includes('node_modules') &&
// Filter out our own internal source code since it'll typically be in node_modules
(!filename.includes('/packages/') || filename.includes('/__tests__/')) &&
!filename.includes('/build/')
);
}

function normalizeCodeLocInfo(str) {
return (
str &&
Expand Down Expand Up @@ -560,7 +571,7 @@ describe('ReactFlightDOMNode', () => {

controller.abort('boom');
resolveGreeting();
const {prelude} = await pendingResult;
const {prelude} = await serverAct(() => pendingResult);
expect(errors).toEqual([]);

function ClientRoot({response}) {
Expand Down Expand Up @@ -711,4 +722,145 @@ describe('ReactFlightDOMNode', () => {
expect(ownerStack).toBeNull();
}
});

// @gate enableHalt && enableAsyncDebugInfo
it('includes deeper location for aborted stacks', async () => {
async function getData() {
const signal = ReactServer.cacheSignal();
await new Promise((resolve, reject) => {
signal.addEventListener('abort', () => reject(signal.reason));
});
}

async function thisShouldNotBeInTheStack() {
await new Promise((resolve, reject) => {
resolve();
});
}

async function Component() {
try {
await getData();
} catch (x) {
await thisShouldNotBeInTheStack(); // This is issued after the rejection so should not be included.
}
return null;
}

function App() {
return ReactServer.createElement(
'html',
null,
ReactServer.createElement(
'body',
null,
ReactServer.createElement(
ReactServer.Suspense,
{fallback: 'Loading...'},
ReactServer.createElement(Component, null),
),
),
);
}

const errors = [];
const serverAbortController = new AbortController();
const {pendingResult} = await serverAct(async () => {
// destructure trick to avoid the act scope from awaiting the returned value
return {
pendingResult: ReactServerDOMStaticServer.unstable_prerender(
ReactServer.createElement(App, null),
webpackMap,
{
signal: serverAbortController.signal,
onError(error) {
errors.push(error);
},
filterStackFrame,
},
),
};
});

await serverAct(
() =>
new Promise(resolve => {
setImmediate(() => {
serverAbortController.abort();
resolve();
});
}),
);

const {prelude} = await pendingResult;

expect(errors).toEqual([]);

function ClientRoot({response}) {
return use(response);
}

const prerenderResponse = ReactServerDOMClient.createFromReadableStream(
await createBufferedUnclosingStream(prelude),
{
serverConsumerManifest: {
moduleMap: null,
moduleLoading: null,
},
},
);

let componentStack;
let ownerStack;

const clientAbortController = new AbortController();

const fizzPrerenderStreamResult = ReactDOMFizzStatic.prerender(
React.createElement(ClientRoot, {response: prerenderResponse}),
{
signal: clientAbortController.signal,
onError(error, errorInfo) {
componentStack = errorInfo.componentStack;
ownerStack = React.captureOwnerStack
? React.captureOwnerStack()
: null;
},
},
);

await await serverAct(
async () =>
new Promise(resolve => {
setImmediate(() => {
clientAbortController.abort();
resolve();
});
}),
);

const fizzPrerenderStream = await fizzPrerenderStreamResult;
const prerenderHTML = await readWebResult(fizzPrerenderStream.prelude);

expect(prerenderHTML).toContain('Loading...');

if (__DEV__) {
expect(normalizeCodeLocInfo(componentStack)).toBe(
'\n in Component (at **)\n in Suspense\n in body\n in html\n in ClientRoot (at **)',
);
} else {
expect(normalizeCodeLocInfo(componentStack)).toBe(
'\n in Suspense\n in body\n in html\n in ClientRoot (at **)',
);
}

if (__DEV__) {
expect(normalizeCodeLocInfo(ownerStack)).toBe(
'\n in getData (at **)' +
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

'\n in Component (at **)' +
'\n in App (at **)',
);
} else {
expect(ownerStack).toBeNull();
}
});
});
1 change: 1 addition & 0 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,7 @@ function pushHaltedAwaitOnComponentStack(
stack: bestStack.debugStack,
};
task.debugTask = (bestStack.debugTask: any);
break;
}
}
}
Expand Down
Loading
Loading