Skip to content

Commit 9b6fdee

Browse files
committed
drop profiler, expose renderTo methods
1 parent a59c1ab commit 9b6fdee

File tree

11 files changed

+245
-178
lines changed

11 files changed

+245
-178
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
],
5050
"dependencies": {
5151
"@testing-library/dom": "^10.4.0",
52+
"@testing-library/react": "^16.0.1",
5253
"jsdom": "^25.0.1",
5354
"rehackt": "^0.1.0"
5455
},

src/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export type { NextRenderOptions, RenderStream } from "./profile/profile.js";
2+
export {
3+
createProfiler,
4+
useTrackRenders,
5+
WaitForRenderTimeoutError,
6+
} from "./profile/profile.js";
7+
8+
export type { SyncScreen } from "./profile/Render.js";
9+
10+
export { renderToRenderStream } from "./renderToRenderStream.js";
11+
export { renderHookToSnapshotStream } from "./renderHookToSnapshotStream.js";

src/jest/ProfiledComponent.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@ import type { MatcherFunction } from "expect";
22
import { WaitForRenderTimeoutError } from "@testing-library/react-render-stream";
33
import type {
44
NextRenderOptions,
5-
Profiler,
6-
ProfiledComponent,
7-
ProfiledHook,
5+
RenderStream,
86
} from "@testing-library/react-render-stream";
97

108
export const toRerender: MatcherFunction<[options?: NextRenderOptions]> =
119
async function (actual, options) {
12-
const _profiler = actual as
13-
| Profiler<any>
14-
| ProfiledComponent<any, any>
15-
| ProfiledHook<any, any>;
16-
const profiler = "Profiler" in _profiler ? _profiler.Profiler : _profiler;
10+
const _profiler = actual as RenderStream<any>;
11+
const profiler =
12+
"Profiler" in _profiler
13+
? (_profiler.Profiler as RenderStream<any>)
14+
: _profiler;
1715
const hint = this.utils.matcherHint("toRerender", "ProfiledComponent", "");
1816
let pass = true;
1917
try {
@@ -44,11 +42,11 @@ const failed = {};
4442
export const toRenderExactlyTimes: MatcherFunction<
4543
[times: number, options?: NextRenderOptions]
4644
> = async function (actual, times, optionsPerRender) {
47-
const _profiler = actual as
48-
| Profiler<any>
49-
| ProfiledComponent<any, any>
50-
| ProfiledHook<any, any>;
51-
const profiler = "Profiler" in _profiler ? _profiler.Profiler : _profiler;
45+
const _profiler = actual as RenderStream<any>;
46+
const profiler =
47+
"Profiler" in _profiler
48+
? (_profiler.Profiler as RenderStream<any>)
49+
: _profiler;
5250
const options = { timeout: 100, ...optionsPerRender };
5351
const hint = this.utils.matcherHint("toRenderExactlyTimes");
5452
let pass = true;

src/jest/index.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,17 @@
11
import { expect } from "@jest/globals";
22
import { toRerender, toRenderExactlyTimes } from "./ProfiledComponent.js";
3-
import type {
4-
NextRenderOptions,
5-
Profiler,
6-
ProfiledComponent,
7-
ProfiledHook,
8-
} from "../profile/index.js";
3+
import type { NextRenderOptions, RenderStream } from "../index.js";
94

105
expect.extend({
116
toRerender,
127
toRenderExactlyTimes,
138
});
149
interface ApolloCustomMatchers<R = void, T = {}> {
15-
toRerender: T extends
16-
| Profiler<any>
17-
| ProfiledComponent<any, any>
18-
| ProfiledHook<any, any>
10+
toRerender: T extends RenderStream<any> | unknown // TODO
1911
? (options?: NextRenderOptions) => Promise<R>
2012
: { error: "matcher needs to be called on a ProfiledComponent instance" };
2113

22-
toRenderExactlyTimes: T extends
23-
| Profiler<any>
24-
| ProfiledComponent<any, any>
25-
| ProfiledHook<any, any>
14+
toRenderExactlyTimes: T extends RenderStream<any> | unknown // TODO
2615
? (count: number, options?: NextRenderOptions) => Promise<R>
2716
: { error: "matcher needs to be called on a ProfiledComponent instance" };
2817
}

src/profile/index.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/profile/profile.tsx

Lines changed: 34 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import { applyStackTrace, captureStackTrace } from "./traces.js";
66
import type { ProfilerContextValue } from "./context.js";
77
import { ProfilerContextProvider, useProfilerContext } from "./context.js";
88
import { disableActWarnings } from "./disableActWarnings.js";
9+
import { render } from "@testing-library/react";
910

10-
type ValidSnapshot = void | (object & { /* not a function */ call?: never });
11+
export type ValidSnapshot =
12+
| void
13+
| (object & { /* not a function */ call?: never });
1114

1215
/** only used for passing around data internally */
1316
const _stackTrace = Symbol();
@@ -17,17 +20,6 @@ export interface NextRenderOptions {
1720
[_stackTrace]?: string;
1821
}
1922

20-
/** @internal */
21-
interface ProfilerProps {
22-
children: React.ReactNode;
23-
}
24-
25-
/** @internal */
26-
export interface Profiler<Snapshot>
27-
extends React.FC<ProfilerProps>,
28-
ProfiledComponentFields<Snapshot>,
29-
ProfiledComponentOnlyFields<Snapshot> {}
30-
3123
interface ReplaceSnapshot<Snapshot> {
3224
(newSnapshot: Snapshot): void;
3325
(updateSnapshot: (lastSnapshot: Readonly<Snapshot>) => Snapshot): void;
@@ -42,13 +34,13 @@ interface MergeSnapshot<Snapshot> {
4234
): void;
4335
}
4436

45-
interface ProfiledComponentOnlyFields<Snapshot> {
37+
export interface ProfiledComponentOnlyFields<Snapshot> {
4638
// Allows for partial updating of the snapshot by shallow merging the results
4739
mergeSnapshot: MergeSnapshot<Snapshot>;
4840
// Performs a full replacement of the snapshot
4941
replaceSnapshot: ReplaceSnapshot<Snapshot>;
5042
}
51-
interface ProfiledComponentFields<Snapshot> {
43+
export interface ProfiledComponentFields<Snapshot> {
5244
/**
5345
* An array of all renders that have happened so far.
5446
* Errors thrown during component render will be captured here, too.
@@ -84,50 +76,16 @@ interface ProfiledComponentFields<Snapshot> {
8476
waitForNextRender(options?: NextRenderOptions): Promise<Render<Snapshot>>;
8577
}
8678

87-
export interface ProfiledComponent<Snapshot extends ValidSnapshot, Props = {}>
88-
extends React.FC<Props>,
89-
ProfiledComponentFields<Snapshot>,
79+
export interface RenderStream<Snapshot extends ValidSnapshot>
80+
extends ProfiledComponentFields<Snapshot>,
9081
ProfiledComponentOnlyFields<Snapshot> {}
9182

92-
/** @internal */
93-
export function profile<Snapshot extends ValidSnapshot = void, Props = {}>({
94-
Component,
95-
...options
96-
}: Parameters<typeof createProfiler<Snapshot>>[0] & {
97-
Component: React.ComponentType<Props>;
98-
}): ProfiledComponent<Snapshot, Props> {
99-
const Profiler = createProfiler(options);
100-
101-
return Object.assign(
102-
function ProfiledComponent(props: Props) {
103-
return (
104-
<Profiler>
105-
<Component {...(props as any)} />
106-
</Profiler>
107-
);
108-
},
109-
{
110-
mergeSnapshot: Profiler.mergeSnapshot,
111-
replaceSnapshot: Profiler.replaceSnapshot,
112-
getCurrentRender: Profiler.getCurrentRender,
113-
peekRender: Profiler.peekRender,
114-
takeRender: Profiler.takeRender,
115-
totalRenderCount: Profiler.totalRenderCount,
116-
waitForNextRender: Profiler.waitForNextRender,
117-
get renders() {
118-
return Profiler.renders;
119-
},
120-
}
121-
);
83+
export interface RenderStreamWithWrapper<Snapshot extends ValidSnapshot>
84+
extends RenderStream<Snapshot> {
85+
Wrapper: React.FC<{ children: React.ReactNode }>;
12286
}
12387

124-
/** @internal */
125-
export function createProfiler<Snapshot extends ValidSnapshot = void>({
126-
onRender,
127-
snapshotDOM = false,
128-
initialSnapshot,
129-
skipNonTrackingRenders,
130-
}: {
88+
export type ProfilerOptions<Snapshot extends ValidSnapshot> = {
13189
onRender?: (
13290
info: BaseRender & {
13391
snapshot: Snapshot;
@@ -142,7 +100,15 @@ export function createProfiler<Snapshot extends ValidSnapshot = void>({
142100
* `useTrackRenders` occured.
143101
*/
144102
skipNonTrackingRenders?: boolean;
145-
} = {}) {
103+
};
104+
105+
/** @internal */
106+
export function createProfiler<Snapshot extends ValidSnapshot = void>({
107+
onRender,
108+
snapshotDOM = false,
109+
initialSnapshot,
110+
skipNonTrackingRenders,
111+
}: ProfilerOptions<Snapshot> = {}): RenderStreamWithWrapper<Snapshot> {
146112
let nextRender: Promise<Render<Snapshot>> | undefined;
147113
let resolveNextRender: ((render: Render<Snapshot>) => void) | undefined;
148114
let rejectNextRender: ((error: unknown) => void) | undefined;
@@ -245,16 +211,17 @@ export function createProfiler<Snapshot extends ValidSnapshot = void>({
245211
};
246212

247213
let iteratorPosition = 0;
248-
const Profiler: Profiler<Snapshot> = Object.assign(
249-
({ children }: ProfilerProps) => {
250-
return (
251-
<ProfilerContextProvider value={profilerContext}>
252-
<React.Profiler id="test" onRender={profilerOnRender}>
253-
{children}
254-
</React.Profiler>
255-
</ProfilerContextProvider>
256-
);
257-
},
214+
function Wrapper({ children }: { children: React.ReactNode }) {
215+
return (
216+
<ProfilerContextProvider value={profilerContext}>
217+
<React.Profiler id="test" onRender={profilerOnRender}>
218+
{children}
219+
</React.Profiler>
220+
</ProfilerContextProvider>
221+
);
222+
}
223+
224+
const Profiler: RenderStreamWithWrapper<Snapshot> = Object.assign(
258225
{
259226
replaceSnapshot,
260227
mergeSnapshot,
@@ -350,7 +317,8 @@ export function createProfiler<Snapshot extends ValidSnapshot = void>({
350317
}
351318
return nextRender;
352319
},
353-
} satisfies ProfiledComponentFields<Snapshot>
320+
} satisfies ProfiledComponentFields<Snapshot>,
321+
{ Wrapper }
354322
);
355323
return Profiler;
356324
}
@@ -363,74 +331,6 @@ export class WaitForRenderTimeoutError extends Error {
363331
}
364332
}
365333

366-
type StringReplaceRenderWithSnapshot<T extends string> =
367-
T extends `${infer Pre}Render${infer Post}` ? `${Pre}Snapshot${Post}` : T;
368-
369-
type ResultReplaceRenderWithSnapshot<T> = T extends (
370-
...args: infer Args
371-
) => Render<infer Snapshot>
372-
? (...args: Args) => Snapshot
373-
: T extends (...args: infer Args) => Promise<Render<infer Snapshot>>
374-
? (...args: Args) => Promise<Snapshot>
375-
: T;
376-
377-
type ProfiledHookFields<ReturnValue> =
378-
ProfiledComponentFields<ReturnValue> extends infer PC
379-
? {
380-
[K in keyof PC as StringReplaceRenderWithSnapshot<
381-
K & string
382-
>]: ResultReplaceRenderWithSnapshot<PC[K]>;
383-
}
384-
: never;
385-
386-
/** @internal */
387-
export interface ProfiledHook<Props, ReturnValue>
388-
extends React.FC<Props>,
389-
ProfiledHookFields<ReturnValue> {
390-
Profiler: Profiler<ReturnValue>;
391-
}
392-
393-
/** @internal */
394-
export function profileHook<ReturnValue extends ValidSnapshot, Props>(
395-
renderCallback: (props: Props) => ReturnValue
396-
): ProfiledHook<Props, ReturnValue> {
397-
const Profiler = createProfiler<ReturnValue>();
398-
399-
const ProfiledHook = (props: Props) => {
400-
Profiler.replaceSnapshot(renderCallback(props));
401-
return null;
402-
};
403-
404-
return Object.assign(
405-
function App(props: Props) {
406-
return (
407-
<Profiler>
408-
<ProfiledHook {...(props as any)} />
409-
</Profiler>
410-
);
411-
},
412-
{
413-
Profiler,
414-
},
415-
{
416-
renders: Profiler.renders,
417-
totalSnapshotCount: Profiler.totalRenderCount,
418-
async peekSnapshot(options) {
419-
return (await Profiler.peekRender(options)).snapshot;
420-
},
421-
async takeSnapshot(options) {
422-
return (await Profiler.takeRender(options)).snapshot;
423-
},
424-
getCurrentSnapshot() {
425-
return Profiler.getCurrentRender().snapshot;
426-
},
427-
async waitForNextSnapshot(options) {
428-
return (await Profiler.waitForNextRender(options)).snapshot;
429-
},
430-
} satisfies ProfiledHookFields<ReturnValue>
431-
);
432-
}
433-
434334
function resolveR18HookOwner(): React.ComponentType | undefined {
435335
return (React as any).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
436336
?.ReactCurrentOwner?.current?.elementType;

0 commit comments

Comments
 (0)