Skip to content

Commit d5cbcd6

Browse files
committed
Add a test for issue #28595
The added test, intended to fail and reproduce the [reported issue](#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. (Maybe 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 d5cbcd6

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

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

+99
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,105 @@ 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 createSuspendedChunk(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 makeDelayedText() {
858+
const {promise, resolve, reject} = createResolvablePromise();
859+
async function DelayedText() {
860+
const data = await promise;
861+
return <div>{data}</div>;
862+
}
863+
return [DelayedText, resolve, reject];
864+
}
865+
866+
const [Posts, resolvePostsData] = makeDelayedText();
867+
const suspendedChunk = createSuspendedChunk(<p>loading</p>);
868+
const {writable, readable} = getTestStream();
869+
const {pipe} = ReactServerDOMServer.renderToPipeableStream(
870+
suspendedChunk.row,
871+
webpackMap,
872+
{
873+
onError(error) {
874+
reportedErrors.push(error);
875+
},
876+
},
877+
);
878+
pipe(writable);
879+
const response = ReactServerDOMClient.createFromReadableStream(readable);
880+
const container = document.createElement('div');
881+
const root = ReactDOMClient.createRoot(container);
882+
883+
function ClientRoot() {
884+
return use(response);
885+
}
886+
887+
await act(() => {
888+
root.render(<ClientRoot />);
889+
});
890+
891+
expect(container.innerHTML).toBe('<p>loading</p>');
892+
893+
const donePromise = createResolvablePromise();
894+
const value = <Posts />;
895+
896+
await act(async () => {
897+
suspendedChunk.resolve({value, done: false, next: donePromise.promise});
898+
await Promise.resolve();
899+
donePromise.resolve({value, done: true});
900+
});
901+
902+
expect(container.innerHTML).toBe('<p>loading</p>');
903+
904+
await act(async () => {
905+
jest.advanceTimersByTime(500);
906+
await resolvePostsData('posts');
907+
await 'the inner async function';
908+
});
909+
910+
expect(container.innerHTML).toBe('<div>posts</div>');
911+
expect(reportedErrors).toEqual([]);
912+
});
913+
815914
it('should preserve state of client components on refetch', async () => {
816915
// Client
817916

0 commit comments

Comments
 (0)