Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 151 additions & 190 deletions packages/bippy/src/core.ts

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions packages/bippy/src/source/inspect-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ const buildTree = (rootStack: StackFrame[], capturedHookLog: HookLogEntry[]): Ho
}

const { primitive } = hook;
const id = NON_ID_HOOK_PRIMITIVES.has(primitive) ? null : nativeHookID++;
const hookId = NON_ID_HOOK_PRIMITIVES.has(primitive) ? null : nativeHookID++;
const isStateEditable = primitive === "Reducer" || primitive === "State";
const name = displayName || primitive;

Expand All @@ -655,7 +655,14 @@ const buildTree = (rootStack: StackFrame[], capturedHookLog: HookLogEntry[]): Ho
fileName: firstStackFrame?.fileName ?? null,
};

levelChildren.push({ id, isStateEditable, name, value: hook.value, subHooks: [], hookSource });
levelChildren.push({
id: hookId,
isStateEditable,
name,
value: hook.value,
subHooks: [],
hookSource,
});
}

processDebugValues(rootChildren, null);
Expand Down
34 changes: 34 additions & 0 deletions packages/bippy/tests/create-fiber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Fiber } from "../src/react-internals/index.js";
import { latestReactWorkTags } from "./react-work-tags.js";

export const createFiber = (overrides: Record<string, unknown> = {}): Fiber => {
const fiber: Fiber = {
alternate: null,
child: null,
childLanes: 0,
deletions: null,
dependencies: null,
elementType: null,
firstEffect: null,
flags: 0,
index: 0,
key: null,
lanes: 0,
lastEffect: null,
memoizedProps: {},
memoizedState: null,
mode: 0,
nextEffect: null,
pendingProps: {},
ref: null,
return: null,
sibling: null,
stateNode: null,
subtreeFlags: 0,
tag: latestReactWorkTags.FunctionComponent,
type: null,
updateQueue: null,
};

return Object.assign(fiber, overrides);
};
18 changes: 18 additions & 0 deletions packages/bippy/tests/create-rdt-hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ReactDevToolsGlobalHook } from "../src/react-internals/index.js";

export const createRDTHook = (overrides: Record<string, unknown> = {}): ReactDevToolsGlobalHook => {
const rdtHook: ReactDevToolsGlobalHook = {
checkDCE: () => {},
hasUnsupportedRendererAttached: false,
inject: () => 1,
on: () => {},
onCommitFiberRoot: () => {},
onCommitFiberUnmount: () => {},
onPostCommitFiberRoot: () => {},
renderers: new Map(),
supportsFiber: true,
supportsFlight: true,
};

return Object.assign(rdtHook, overrides);
};
11 changes: 11 additions & 0 deletions packages/bippy/tests/create-react-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ReactRenderer } from "../src/react-internals/index.js";

export const createReactRenderer = (overrides: Record<string, unknown> = {}): ReactRenderer => {
const renderer: ReactRenderer = {
bundleType: 1,
rendererPackageName: "test-renderer",
version: "19.0.0",
};

return Object.assign(renderer, overrides);
};
13 changes: 9 additions & 4 deletions packages/bippy/tests/did-fiber-commit.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import "../src/index.js"; // KEEP THIS LINE ON TOP
import "../src/index.js"; // HACK: Bippy must initialize before imports that load React.

import { expect, it } from "vite-plus/test";
import React from "react";

import { didFiberCommit, Fiber, instrument } from "../src/index.js";
import { didFiberCommit, instrument, type Fiber } from "../src/index.js";
import { render } from "@testing-library/react";
import { requireFiber } from "./require-fiber.js";

const Example = () => {
return <div>Hello</div>;
Expand All @@ -27,7 +28,9 @@ it("should return true for a fiber that has committed", () => {
});
render(<ExampleWithUnmount />);
expect(maybeRenderedFiber).not.toBeNull();
expect(didFiberCommit(maybeRenderedFiber as unknown as Fiber)).toBe(true);
expect(didFiberCommit(requireFiber(maybeRenderedFiber, "React DOM did not render a Fiber"))).toBe(
true,
);
});

it("should return false for a fiber that hasn't committed", () => {
Expand All @@ -39,5 +42,7 @@ it("should return false for a fiber that hasn't committed", () => {
});
render(<Example />);
expect(maybeRenderedFiber).not.toBeNull();
expect(didFiberCommit(maybeRenderedFiber as unknown as Fiber)).toBe(false);
expect(didFiberCommit(requireFiber(maybeRenderedFiber, "React DOM did not render a Fiber"))).toBe(
false,
);
});
18 changes: 3 additions & 15 deletions packages/bippy/tests/did-fiber-render.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import "../src/index.js"; // KEEP THIS LINE ON TOP
import "../src/index.js"; // HACK: Bippy must initialize before imports that load React.

import { expect, it } from "vite-plus/test";
import React from "react";

import { didFiberRender, instrument } from "../src/index.js";
import type { Fiber, WorkTag } from "../src/index.js";
import { ReactFiberFlags } from "../src/react-internals/index.js";
import { createFiber } from "./create-fiber.js";
import { latestReactWorkTags } from "./react-work-tags.js";
import { render } from "@testing-library/react";

Expand Down Expand Up @@ -35,20 +36,7 @@ it("should return true for a fiber that has rendered", () => {
});

const createMockFiber = (tag: WorkTag, flags: number | undefined, effectTag?: number): Fiber =>
({
alternate: null,
child: null,
effectTag,
flags,
memoizedProps: {},
memoizedState: null,
pendingProps: {},
return: null,
sibling: null,
stateNode: null,
tag,
type: () => null,
}) as unknown as Fiber;
createFiber({ effectTag, flags, tag, type: () => null });

it("should check the PerformedWork flag for every composite tag", () => {
expect(
Expand Down
27 changes: 9 additions & 18 deletions packages/bippy/tests/fiber-id.test.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,36 @@
import { expect, it } from "vite-plus/test";
import { getFiberId, setFiberId } from "../src/index.js";
import type { Fiber } from "../src/react-internals/index.js";
import { latestReactWorkTags } from "./react-work-tags.js";
import { createFiber } from "./create-fiber.js";

const createMockFiber = (alternate: Fiber | null = null): Fiber =>
({
alternate,
child: null,
flags: 0,
return: null,
sibling: null,
stateNode: null,
tag: latestReactWorkTags.FunctionComponent,
type: null,
}) as unknown as Fiber;
const createFiberWithAlternate = (alternate: Fiber | null = null): Fiber =>
createFiber({ alternate });

it("should assign a stable auto-incremented id", () => {
const fiber = createMockFiber();
const fiber = createFiberWithAlternate();
setFiberId(fiber);
const assignedId = getFiberId(fiber);
expect(assignedId).toBeTypeOf("number");
expect(getFiberId(fiber)).toBe(assignedId);
});

it("should honor an explicitly assigned id", () => {
const fiber = createMockFiber();
const fiber = createFiberWithAlternate();
setFiberId(fiber, 12_345);
expect(getFiberId(fiber)).toBe(12_345);
});

it("should advance generated ids past explicitly assigned ids", () => {
const explicitFiber = createMockFiber();
const generatedFiber = createMockFiber();
const explicitFiber = createFiberWithAlternate();
const generatedFiber = createFiberWithAlternate();
setFiberId(explicitFiber, 1_000_000_000);
setFiberId(generatedFiber);
expect(getFiberId(generatedFiber)).toBeGreaterThan(1_000_000_000);
});

it("should reuse the id of the alternate fiber", () => {
const currentFiber = createMockFiber();
const currentFiber = createFiberWithAlternate();
setFiberId(currentFiber, 0);
const alternateFiber = createMockFiber(currentFiber);
const alternateFiber = createFiberWithAlternate(currentFiber);
expect(getFiberId(alternateFiber)).toBe(0);
});
10 changes: 2 additions & 8 deletions packages/bippy/tests/get-display-name-from-source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { encode } from "@jridgewell/sourcemap-codec";
import { describe, expect, it } from "vite-plus/test";
import type { Fiber } from "../src/react-internals/index.js";
import { getDisplayNameFromSource } from "../src/source/get-display-name-from-source.js";
import { createFiber } from "./create-fiber.js";
import { latestReactWorkTags } from "./react-work-tags.js";
import { sourceFetch as failingFetchFn } from "./source-fetch.js";

Expand All @@ -18,14 +19,7 @@ const createThrowingComponent = (componentName: string): (() => null) => {
return component;
};

const createFakeFiber = (tag: number, type: unknown): Fiber =>
({
tag,
type,
return: null,
child: null,
sibling: null,
}) as unknown as Fiber;
const createFakeFiber = (tag: number, type: unknown): Fiber => createFiber({ tag, type });

interface FixedPointMapOptions {
mappedName?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/bippy/tests/get-display-name.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import "../src/index.js"; // KEEP THIS LINE ON TOP
import "../src/index.js"; // HACK: Bippy must initialize before imports that load React.

import { expect, it } from "vite-plus/test";
import React, { forwardRef, memo, Component } from "react";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// intentionally avoids importing ../index.js so no rdt hook is installed
// HACK: Avoid importing index so the missing-hook path remains observable.
import { expect, it } from "vite-plus/test";
import { getFiberFromHostInstance } from "../src/core.js";

Expand Down
52 changes: 17 additions & 35 deletions packages/bippy/tests/get-fiber-from-host-instance.test.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
import "../src/index.js"; // KEEP THIS LINE ON TOP
import "../src/index.js"; // HACK: Bippy must initialize before imports that load React.

import * as ReactThreeTestRenderer from "@react-three/test-renderer";
import { render, screen } from "@testing-library/react";
import React from "react";
import { expect, it } from "vite-plus/test";
import { getFiberFromHostInstance, getRDTHook, instrument, traverseFiber } from "../src/index.js";
import type { Fiber, FiberRoot, ReactRenderer, WorkTag } from "../src/react-internals/index.js";
import type { Fiber, FiberRoot } from "../src/react-internals/index.js";
import { createFiber } from "./create-fiber.js";
import { createReactRenderer } from "./create-react-renderer.js";
import { latestReactWorkTags } from "./react-work-tags.js";

interface MockHostFiber {
child: MockHostFiber | null;
flags: number;
memoizedState?: Record<string, unknown>;
pendingProps: Record<string, unknown>;
return: MockHostFiber | null;
sibling: MockHostFiber | null;
stateNode: unknown;
tag: WorkTag;
type: string;
}

const createMockHostFiber = (stateNode: unknown, type = "RCTView"): MockHostFiber => ({
child: null,
flags: 0,
pendingProps: {},
return: null,
sibling: null,
stateNode,
tag: latestReactWorkTags.HostComponent,
type,
});
const createMockHostFiber = (stateNode: unknown, type = "RCTView"): Fiber =>
createFiber({ stateNode, tag: latestReactWorkTags.HostComponent, type });

it("should return the fiber from the host instance", () => {
render(<div>HostInstance</div>);
Expand Down Expand Up @@ -77,15 +59,15 @@ it("should resolve Fabric public instances from canonical host state", () => {
const rootFiber = createMockHostFiber(null, "root");
rootFiber.tag = latestReactWorkTags.HostRoot;
rootFiber.child = hostFiber;
rootFiber.memoizedState = { element: {} };
rootFiber.memoizedState = { element: {}, memoizedState: null, next: null };
hostFiber.return = rootFiber;
const fiberRoot = { current: rootFiber };
getRDTHook().onCommitFiberRoot(999, fiberRoot as unknown as FiberRoot, undefined, false);
getRDTHook().onCommitFiberRoot(999, fiberRoot, undefined, false);
try {
expect(getFiberFromHostInstance(publicInstance)).toBe(hostFiber);
} finally {
rootFiber.memoizedState = { element: null };
getRDTHook().onCommitFiberRoot(999, fiberRoot as unknown as FiberRoot, undefined, false);
rootFiber.memoizedState = { element: null, memoizedState: null, next: null };
getRDTHook().onCommitFiberRoot(999, fiberRoot, undefined, false);
unsubscribe();
}
});
Expand All @@ -96,24 +78,24 @@ it("should resolve Paper native tags from host state", () => {
const rootFiber = createMockHostFiber(null, "root");
rootFiber.tag = latestReactWorkTags.HostRoot;
rootFiber.child = hostFiber;
rootFiber.memoizedState = { element: {} };
rootFiber.memoizedState = { element: {}, memoizedState: null, next: null };
hostFiber.return = rootFiber;
const fiberRoot = { current: rootFiber };
getRDTHook().onCommitFiberRoot(999, fiberRoot as unknown as FiberRoot, undefined, false);
getRDTHook().onCommitFiberRoot(999, fiberRoot, undefined, false);
try {
expect(getFiberFromHostInstance(42)).toBe(hostFiber);
} finally {
rootFiber.memoizedState = { element: null };
getRDTHook().onCommitFiberRoot(999, fiberRoot as unknown as FiberRoot, undefined, false);
rootFiber.memoizedState = { element: null, memoizedState: null, next: null };
getRDTHook().onCommitFiberRoot(999, fiberRoot, undefined, false);
unsubscribe();
}
});

it("should prefer renderer.findFiberByHostInstance when available", () => {
const mockFiber = { tag: latestReactWorkTags.HostComponent, type: "span" } as unknown as Fiber;
const mockFiber = createFiber({ tag: latestReactWorkTags.HostComponent, type: "span" });
const rdtHook = getRDTHook();
const findFiberByHostInstance = () => mockFiber;
rdtHook.renderers.set(999, { findFiberByHostInstance } as unknown as ReactRenderer);
rdtHook.renderers.set(999, createReactRenderer({ findFiberByHostInstance }));
try {
expect(getFiberFromHostInstance({})).toBe(mockFiber);
} finally {
Expand All @@ -126,7 +108,7 @@ it("should ignore renderers whose findFiberByHostInstance throws", () => {
const findFiberByHostInstance = () => {
throw new Error("no fiber");
};
rdtHook.renderers.set(999, { findFiberByHostInstance } as unknown as ReactRenderer);
rdtHook.renderers.set(999, createReactRenderer({ findFiberByHostInstance }));
try {
expect(getFiberFromHostInstance({})).toBe(null);
} finally {
Expand Down
10 changes: 7 additions & 3 deletions packages/bippy/tests/get-fiber-stack.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import "../src/index.js"; // KEEP THIS LINE ON TOP
import "../src/index.js"; // HACK: Bippy must initialize before imports that load React.

import { render } from "@testing-library/react";
import React from "react";
import { expect, it } from "vite-plus/test";
import { Fiber, getFiberStack, instrument } from "../src/index.js";
import { getFiberStack, instrument, type Fiber } from "../src/index.js";

interface ExampleWithChildrenPropProps {
children: React.ReactNode;
}

export const Example = () => {
return <div>Hello</div>;
};

export const ExampleWithChildrenProp = ({ children }: { children: React.ReactNode }) => {
export const ExampleWithChildrenProp = ({ children }: ExampleWithChildrenPropProps) => {
return <div>{children}</div>;
};

Expand Down
Loading
Loading