Skip to content

Commit 08444e2

Browse files
committed
Add a test for issue facebook#28595
The added test, intended to fail and reproduce the [reported issue](facebook#28595), unexpectedly passes in its current state. I see three possible reasons: 1. The bug report could be invalid. 2. How I've structured the test might be insufficient to replicate what `ai/rsc` is doing. 3. Something in the test setup could be masking the actual error. (Possibly related to fake timers?) If the problem lies in reason 2 or 3, this test could possibly serve as a foundation for further investigation.
1 parent a493901 commit 08444e2

File tree

1 file changed

+105
-0
lines changed

1 file changed

+105
-0
lines changed

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,111 @@ describe('ReactFlightDOM', () => {
812812
expect(reportedErrors).toEqual([]);
813813
});
814814

815+
it('should handle streaming async server components', async () => {
816+
const reportedErrors = [];
817+
818+
const Row = async ({current, next}) => {
819+
const chunk = await next;
820+
821+
if (chunk.done) {
822+
return chunk.value;
823+
}
824+
825+
return (
826+
<Suspense fallback={chunk.value}>
827+
<Row current={chunk.value} next={chunk.next} />
828+
</Suspense>
829+
);
830+
};
831+
832+
function createResolvablePromise() {
833+
let _resolve, _reject;
834+
835+
const promise = new Promise((resolve, reject) => {
836+
_resolve = resolve;
837+
_reject = reject;
838+
});
839+
840+
return {promise, resolve: _resolve, reject: _reject};
841+
}
842+
843+
function createSuspensedChunk(initialValue) {
844+
const {promise, resolve, reject} = createResolvablePromise();
845+
846+
return {
847+
row: (
848+
<Suspense fallback={initialValue}>
849+
<Row current={initialValue} next={promise} />
850+
</Suspense>
851+
),
852+
resolve,
853+
reject,
854+
};
855+
}
856+
857+
function Text({children}) {
858+
return <div>{children}</div>;
859+
}
860+
861+
function makeDelayedText() {
862+
const {promise, resolve, reject} = createResolvablePromise();
863+
async function DelayedText() {
864+
const data = await promise;
865+
return <Text>{data}</Text>;
866+
}
867+
return [DelayedText, resolve, reject];
868+
}
869+
870+
const [Posts, resolvePostsData] = makeDelayedText();
871+
const suspendedChunk = createSuspensedChunk(<p>loading</p>);
872+
const model = {rootContent: suspendedChunk.row};
873+
874+
function ProfilePage({response}) {
875+
return use(response).rootContent;
876+
}
877+
878+
const {writable, readable} = getTestStream();
879+
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
880+
model,
881+
webpackMap,
882+
{
883+
onError(error) {
884+
reportedErrors.push(error);
885+
},
886+
},
887+
);
888+
pipe(writable);
889+
const response = ReactServerDOMClient.createFromReadableStream(readable);
890+
const container = document.createElement('div');
891+
const root = ReactDOMClient.createRoot(container);
892+
893+
await act(() => {
894+
root.render(<ProfilePage response={response} />);
895+
});
896+
897+
expect(container.innerHTML).toBe('<p>loading</p>');
898+
899+
const resolvable = createResolvablePromise();
900+
const value = <Posts />;
901+
902+
await act(async () => {
903+
suspendedChunk.resolve({value, done: false, next: resolvable.promise});
904+
await Promise.resolve();
905+
resolvable.resolve({value, done: true});
906+
});
907+
908+
expect(container.innerHTML).toBe('<p>loading</p>');
909+
910+
await act(async () => {
911+
jest.advanceTimersByTime(500);
912+
await resolvePostsData('posts');
913+
await 'the inner async function';
914+
});
915+
916+
expect(container.innerHTML).toBe('<div>posts</div>');
917+
expect(reportedErrors).toEqual([]);
918+
});
919+
815920
it('should preserve state of client components on refetch', async () => {
816921
// Client
817922

0 commit comments

Comments
 (0)