diff --git a/packages/react-dom/src/__tests__/ReactDOMViewTransition-test.js b/packages/react-dom/src/__tests__/ReactDOMViewTransition-test.js new file mode 100644 index 00000000000..1c5b43a18ac --- /dev/null +++ b/packages/react-dom/src/__tests__/ReactDOMViewTransition-test.js @@ -0,0 +1,179 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactDOMClient; +let Suspense; +let SuspenseList; +let ViewTransition; +let act; +let assertLog; +let Scheduler; +let textCache; + +describe('ReactDOMViewTransition', () => { + let container; + + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactDOMClient = require('react-dom/client'); + Scheduler = require('scheduler'); + act = require('internal-test-utils').act; + assertLog = require('internal-test-utils').assertLog; + Suspense = React.Suspense; + ViewTransition = React.ViewTransition; + if (gate(flags => flags.enableSuspenseList)) { + SuspenseList = React.unstable_SuspenseList; + } + container = document.createElement('div'); + document.body.appendChild(container); + + textCache = new Map(); + }); + + afterEach(() => { + document.body.removeChild(container); + }); + + function resolveText(text) { + const record = textCache.get(text); + if (record === undefined) { + const newRecord = { + status: 'resolved', + value: text, + }; + textCache.set(text, newRecord); + } else if (record.status === 'pending') { + const thenable = record.value; + record.status = 'resolved'; + record.value = text; + thenable.pings.forEach(t => t()); + } + } + + function readText(text) { + const record = textCache.get(text); + if (record !== undefined) { + switch (record.status) { + case 'pending': + Scheduler.log(`Suspend! [${text}]`); + throw record.value; + case 'rejected': + throw record.value; + case 'resolved': + return record.value; + } + } else { + Scheduler.log(`Suspend! [${text}]`); + const thenable = { + pings: [], + then(resolve) { + if (newRecord.status === 'pending') { + thenable.pings.push(resolve); + } else { + Promise.resolve().then(() => resolve(newRecord.value)); + } + }, + }; + + const newRecord = { + status: 'pending', + value: thenable, + }; + textCache.set(text, newRecord); + + throw thenable; + } + } + + function Text({text}) { + Scheduler.log(text); + return text; + } + + function AsyncText({text}) { + readText(text); + Scheduler.log(text); + return text; + } + + // @gate enableSuspenseList + it('handles ViewTransition wrapping Suspense inside SuspenseList', async () => { + function Card({id}) { + return ( +