Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
lunaruan committed Apr 14, 2022
1 parent 39eb6c8 commit 65643b0
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 8 deletions.
10 changes: 2 additions & 8 deletions packages/react-reconciler/src/ReactFiberCommitWork.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,11 @@ import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import type {UpdateQueue} from './ReactUpdateQueue.new';
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
import type {Wakeable} from 'shared/ReactTypes';
import type {
OffscreenState,
OffscreenInstance,
} from './ReactFiberOffscreenComponent';
import type {OffscreenState} from './ReactFiberOffscreenComponent';
import type {HookFlags} from './ReactHookEffectTags';
import type {Cache} from './ReactFiberCacheComponent.new';
import type {RootState} from './ReactFiberRoot.new';
import type {
Transition,
PendingSuspenseBoundaries,
} from './ReactFiberTracingMarkerComponent.new';
import type {Transition} from './ReactFiberTracingMarkerComponent.new';

import {
enableCreateEventHandleAPI,
Expand Down
181 changes: 181 additions & 0 deletions packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ let ReactNoop;
let Scheduler;
let act;

let getCacheForType;
let useState;
let Suspense;
let startTransition;

let caches;
let seededCache;

describe('ReactInteractionTracing', () => {
beforeEach(() => {
jest.resetModules();
Expand All @@ -28,13 +33,121 @@ describe('ReactInteractionTracing', () => {

useState = React.useState;
startTransition = React.startTransition;
Suspense = React.Suspense;

getCacheForType = React.unstable_getCacheForType;

caches = [];
seededCache = null;
});

function createTextCache() {
if (seededCache !== null) {
const cache = seededCache;
seededCache = null;
return cache;
}

const data = new Map();
const cache = {
data,
resolve(text) {
const record = data.get(text);

if (record === undefined) {
const newRecord = {
status: 'resolved',
value: text,
};
data.set(text, newRecord);
} else if (record.status === 'pending') {
const thenable = record.value;
record.status = 'resolved';
record.value = text;
thenable.pings.forEach(t => t());
}
},
reject(text, error) {
const record = data.get(text);
if (record === undefined) {
const newRecord = {
status: 'rejected',
value: error,
};
data.set(text, newRecord);
} else if (record.status === 'pending') {
const thenable = record.value;
record.status = 'rejected';
record.value = error;
thenable.pings.forEach(t => t());
}
},
};
caches.push(cache);
return cache;
}

function readText(text) {
const textCache = getCacheForType(createTextCache);
const record = textCache.data.get(text);
if (record !== undefined) {
switch (record.status) {
case 'pending':
Scheduler.unstable_yieldValue(`Suspend [${text}]`);
throw record.value;
case 'rejected':
Scheduler.unstable_yieldValue(`Error [${text}]`);
throw record.value;
case 'resolved':
return record.value;
}
} else {
Scheduler.unstable_yieldValue(`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.data.set(text, newRecord);

throw thenable;
}
}

function AsyncText({text}) {
const fullText = readText(text);
Scheduler.unstable_yieldValue(fullText);
return fullText;
}

function Text({text}) {
Scheduler.unstable_yieldValue(text);
return text;
}

function resolveMostRecentTextCache(text) {
if (caches.length === 0) {
throw Error('Cache does not exist');
} else {
// Resolve the most recently created cache. An older cache can by
// resolved with `caches[index].resolve(text)`.
caches[caches.length - 1].resolve(text);
}
}

const resolveText = resolveMostRecentTextCache;

function advanceTimers(ms) {
// Note: This advances Jest's virtual time but not React's. Use
// ReactNoop.expire for that.
Expand Down Expand Up @@ -98,4 +211,72 @@ describe('ReactInteractionTracing', () => {
});
});
});

// @gate enableTransitionTracing
it('should correctly trace interactions for async roots', async () => {
const transitionCallbacks = {
onTransitionStart: (name, startTime) => {
Scheduler.unstable_yieldValue(
`onTransitionStart(${name}, ${startTime})`,
);
},
onTransitionComplete: (name, startTime, endTime) => {
Scheduler.unstable_yieldValue(
`onTransitionComplete(${name}, ${startTime}, ${endTime})`,
);
},
};
let navigateToPageTwo;
function App() {
const [navigate, setNavigate] = useState(false);
navigateToPageTwo = () => {
setNavigate(true);
};

return (
<div>
{navigate ? (
<Suspense
fallback={<Text text="Loading..." />}
name="suspense page">
<AsyncText text="Page Two" />
</Suspense>
) : (
<Text text="Page One" />
)}
</div>
);
}

const root = ReactNoop.createRoot({transitionCallbacks});
await act(async () => {
root.render(<App />);
ReactNoop.expire(1000);
await advanceTimers(1000);

expect(Scheduler).toFlushAndYield(['Page One']);
});

await act(async () => {
startTransition(() => navigateToPageTwo(), {name: 'page transition'});

ReactNoop.expire(1000);
await advanceTimers(1000);

expect(Scheduler).toFlushAndYield([
'Suspend [Page Two]',
'Loading...',
'onTransitionStart(page transition, 1000)',
]);

ReactNoop.expire(1000);
await advanceTimers(1000);
await resolveText('Page Two');

expect(Scheduler).toFlushAndYield([
'Page Two',
'onTransitionComplete(page transition, 1000, 3000)',
]);
});
});
});

0 comments on commit 65643b0

Please sign in to comment.