Skip to content
Merged
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
4,122 changes: 2,018 additions & 2,104 deletions package-lock.json

Large diffs are not rendered by default.

30 changes: 13 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,42 +48,38 @@
"author": "Marvin Hagemeister <hello@marvinh.dev>",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.18.10",
"@babel/plugin-syntax-typescript": "^7.18.6",
"@babel/plugin-transform-react-jsx": "^7.18.10",
"@babel/core": "^7.29.0",
"@babel/plugin-syntax-typescript": "^7.28.6",
"@babel/plugin-transform-react-jsx": "^7.28.6",
"@playwright/test": "^1.59.1",
"@preact/signals": "^1.2.3",
"@prefresh/vite": "^2.4.5",
"@preact/signals": "^2.9.0",
"@prefresh/vite": "^3.0.0",
"@testing-library/preact": "^3.2.3",
"@types/archiver": "^5.3.1",
"@types/archiver": "^7.0.0",
"@types/babel__core": "^7.1.19",
"@types/chrome": "^0.0.310",
"@types/mri": "^1.1.1",
"@types/node": "^25.6.0",
"@types/node-fetch": "^2.6.2",
"@types/sinon": "^17.0.4",
"@types/tar": "^6.1.2",
"archiver": "^5.3.1",
"@types/tar": "^7.0.87",
"archiver": "^7.0.1",
"babel-plugin-helpers": "^0.1.1",
"babel-plugin-transform-define": "^2.1.0",
"babel-plugin-transform-jsx-to-htm": "^2.2.0",
"babel-plugin-transform-rename-properties": "^0.1.0",
"cross-env": "^7.0.2",
"cross-env": "^10.1.0",
"esbuild": "^0.28.0",
"fs-extra": "^10.1.0",
"jsdom": "^26.0.0",
"jsdom": "^29.1.1",
"kolorist": "^1.5.1",
"lightningcss": "^1.24.1",
"mri": "^1.2.0",
"oxfmt": "^0.48.0",
"oxlint": "^1.63.0",
"preact": "^10.25.1",
"sinon": "^19.0.4",
"tar": "^6.1.11",
"typescript": "^5.4.5",
"tar": "^7.5.14",
"typescript": "^6.0.3",
"vite": "^8.0.10",
"vitest": "^4.1.5",
"web-ext": "^8.5.0"
"web-ext": "^10.1.0"
},
"dependencies": {
"errorstacks": "^2.4.1",
Expand Down
29 changes: 19 additions & 10 deletions src/adapter/10/bindings.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { HookType } from "../shared/hooks";
import type { Component, VNode } from "preact";
import type {
Component as IComponent,
VNode as IVNode,
} from "preact/src/internal";
import { ComponentHooks, HookState, PreactBindings } from "../shared/bindings";
import { RendererConfig } from "../shared/renderer";
import { getRenderReasonPost } from "./renderReason";

type InternalComponent = Component & {
_nextState?: unknown;
};

type InternalVNode = VNode & {
_parent?: VNode | null;
_component?: Component | null;
_dom?: HTMLElement | Text | null;
_children?: Array<VNode | null | undefined>;
};

// Mangle accessors

/**
* Get the direct parent of a `vnode`
*/
export function getVNodeParent(vnode: VNode): VNode | null {
return (
(vnode as IVNode)._parent ||
(vnode as InternalVNode)._parent ||
(vnode as any).__ ||
// Older Preact X versions used `__p`
(vnode as any).__p ||
Expand All @@ -34,14 +41,16 @@ export function isRoot(vnode: VNode, config: RendererConfig): boolean {
* Return the component instance of a `vnode` or `hookState`
*/
export function getComponent(node: HookState | VNode): Component | null {
return (node as HookState | IVNode)._component || (node as any).__c || null;
return (
(node as HookState | InternalVNode)._component || (node as any).__c || null
);
}

/**
* Get a `vnode`'s _dom reference.
*/
export function getDom(vnode: VNode): HTMLElement | Text | null {
return (vnode as IVNode)._dom || (vnode as any).__e || null;
return (vnode as InternalVNode)._dom || (vnode as any).__e || null;
}

export function hasDom(x: any): boolean {
Expand Down Expand Up @@ -172,7 +181,7 @@ export function getHookState(
export function getActualChildren(
vnode: VNode,
): Array<VNode | null | undefined> {
return (vnode as IVNode)._children || (vnode as any).__k || [];
return (vnode as InternalVNode)._children || (vnode as any).__k || [];
}

// End Mangle accessors
Expand Down Expand Up @@ -239,11 +248,11 @@ export function getDisplayName(vnode: VNode, config: RendererConfig): string {
}

export function getNextState<S>(c: Component): S {
return (c as IComponent)._nextState || (c as any).__s || null;
return (c as InternalComponent)._nextState || (c as any).__s || null;
}

export function setNextState<S>(c: Component, value: S): S {
return ((c as IComponent)._nextState = (c as any).__s = value);
return ((c as InternalComponent)._nextState = (c as any).__s = value);
}

function getSuspenseStateKey(c: Component<any, any>) {
Expand Down
9 changes: 4 additions & 5 deletions src/adapter/protocol/events.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expect } from "vitest";
import { expect, vi } from "vitest";
import { applyEvent } from "./events";
import * as sinon from "sinon";
import { createStore } from "../../view/store";
import { fromSnapshot } from "../debug";

Expand Down Expand Up @@ -217,7 +216,7 @@ describe("applyEvent", () => {
});

it("should update inspect data when inspected node is updated", () => {
const spy = sinon.spy();
const spy = vi.fn();
const store = createStore();
store.subscribe(spy);

Expand All @@ -243,8 +242,8 @@ describe("applyEvent", () => {
]);
applyEvent(store, "operation_v2", data);

expect(spy.callCount).to.equal(1);
expect(spy.args[0]).to.deep.equal(["inspect", 2]);
expect(spy).toHaveBeenCalledTimes(1);
expect(spy.mock.calls[0]).to.deep.equal(["inspect", 2]);
});

it("should reset uncollapsed state", () => {
Expand Down
2 changes: 1 addition & 1 deletion src/adapter/protocol/events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { flushTable, StringTable } from "./string-table";
import { Store } from "../../view/store/types";
import { batch } from "@preact/signals-core";
import { batch } from "@preact/signals";
import { recordProfilerCommit } from "../../view/components/profiler/data/commits";
import { ops2Tree } from "./operations";
import { applyOperationsV1 } from "./legacy/operationsV1";
Expand Down
4 changes: 3 additions & 1 deletion src/adapter/shared/inspectVNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export function inspectVNode<T extends SharedVNode>(
Object.keys(c.state).length > 0;

const isSignalTextNode =
typeof vnode.type === "function" && vnode.type.displayName === "_st";
typeof vnode.type === "function" &&
(vnode.type.displayName === "_st" ||
vnode.type.displayName === "ReactiveTextNode");

const hasHooks =
c != null && !isSignalTextNode && bindings.getComponentHooks(vnode) != null;
Expand Down
11 changes: 5 additions & 6 deletions src/adapter/shared/renderer.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { h, render, Options, options, Fragment, Component } from "preact";
import * as sinon from "sinon";
import { createRenderer } from "./renderer";
import { setupOptionsV10 } from "../10/options";
import { expect } from "vitest";
import { expect, vi } from "vitest";
import { toSnapshot } from "../debug";
import { useState } from "preact/hooks";
import { act } from "preact/test-utils";
Expand All @@ -21,7 +20,7 @@ export function setupScratch() {
}

export function setupMockHook(options: Options) {
const spy = sinon.spy();
const spy = vi.fn();
const roots = new Map();
const renderer = createRenderer(
{ send: spy, listen: () => null, listenToPage: () => null },
Expand All @@ -45,14 +44,14 @@ export function setupMockHook(options: Options) {
};
}

function getOps(spy: sinon.SinonSpy) {
return spy.args.filter(arg => arg[0] === "operation_v2");
function getOps(spy: ReturnType<typeof vi.fn>) {
return spy.mock.calls.filter(arg => arg[0] === "operation_v2");
}

describe("Renderer 10", () => {
let scratch: HTMLDivElement;
let destroy: () => void;
let spy: sinon.SinonSpy;
let spy: ReturnType<typeof vi.fn>;
let renderer: Renderer;

beforeEach(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/adapter/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PreactBindings, SharedVNode } from "./bindings";

export function getSignalTextName(name: string) {
return name === "_st" ? "__TextSignal" : name;
return name === "_st" || name === "ReactiveTextNode" ? "__TextSignal" : name;
}

export function traverse<T extends SharedVNode>(
Expand Down
9 changes: 4 additions & 5 deletions src/adapter/store.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { createStore } from "../view/store";
import { applyOperationsV2 } from "./protocol/events";
import { expect } from "vitest";
import { expect, vi } from "vitest";
import { fromSnapshot } from "./debug";
import * as sinon from "sinon";
import { effect } from "@preact/signals";

describe("Store", () => {
Expand Down Expand Up @@ -54,9 +53,9 @@ describe("Store", () => {
]);
applyOperationsV2(store, event);

const spy = sinon.spy();
const spy = vi.fn();
effect(() => spy(store.nodeList.value));
spy.resetHistory();
spy.mockClear();

// prettier-ignore
const event2 = fromSnapshot([
Expand All @@ -66,7 +65,7 @@ describe("Store", () => {
]);
applyOperationsV2(store, event2);

expect(spy.callCount).to.eq(2); // TODO: Should be called once
expect(spy).toHaveBeenCalledTimes(2); // TODO: Should be called once
expect(store.nodes.value.get(1)!.children).to.deep.equal([]);
expect(store.nodes.value.get(2)).to.equal(undefined);
expect(store.nodes.value.get(3)).to.equal(undefined);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { h, ComponentChild } from "preact";
import { expect } from "vitest";
import { expect, vi } from "vitest";
import { CommitTimeline } from "./CommitTimeline";
import { fireEvent, render } from "@testing-library/preact";
import * as sinon from "sinon";

export function renderTest(ui: ComponentChild) {
const res = render(ui);
Expand Down Expand Up @@ -34,7 +33,7 @@ describe("CommitTimeline", () => {
});

it("should wrap around if selection === 0", () => {
const spy = sinon.spy();
const spy = vi.fn();
const { $ } = renderTest(
<CommitTimeline
onChange={spy}
Expand All @@ -45,11 +44,11 @@ describe("CommitTimeline", () => {

const btn = $("[data-testid='prev-commit']")!;
fireEvent.click(btn);
expect(spy.args[0][0]).to.equal(3);
expect(spy.mock.calls[0][0]).to.equal(3);
});

it("should wrap around if selection === items.length", () => {
const spy = sinon.spy();
const spy = vi.fn();
const { $ } = renderTest(
<CommitTimeline
onChange={spy}
Expand All @@ -61,6 +60,6 @@ describe("CommitTimeline", () => {
const btn = $("[data-testid='next-commit']")!;
fireEvent.click(btn);

expect(spy.args[0][0]).to.equal(0);
expect(spy.mock.calls[0][0]).to.equal(0);
});
});
19 changes: 16 additions & 3 deletions test-e2e/pw-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,22 @@ export async function gotoTest(
if (fixtureError) {
throw new Error(`Fixture page error: ${(fixtureError as any).message}`);
}
const ready = await page.evaluate(
() => (window as any).__PREACT_E2E_READY__ === true,
);
let ready = false;
try {
ready = await page.evaluate(
() => (window as any).__PREACT_E2E_READY__ === true,
);
} catch (err) {
// The fixture page can briefly navigate while the query-string driven
// fixture/preact selectors settle. Retry until the normal ready timeout.
if (
!String((err as Error).message).includes(
"Execution context was destroyed",
)
) {
throw err;
}
}
if (ready) break;
if (Date.now() > deadline) {
throw new Error(
Expand Down
2 changes: 2 additions & 0 deletions test-e2e/tests/inspect-signal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ test("Show computed signal as readonly", async ({ page }) => {
test("Show signals in hooks", async ({ page }) => {
const { devtools } = await gotoTest(page, "signals");

await page.locator("button:has-text('force update')").click();
await devtools.click(locateTreeItem("Counter"));

await devtools.waitForSelector('[data-testid="props-row"]');
Expand Down Expand Up @@ -107,6 +108,7 @@ test("Don't crash when signal hook is updated", async ({ page }) => {
);
const { devtools } = await gotoTest(page, "signals");

await page.locator("button:has-text('force update')").click();
await devtools.click(locateTreeItem("Counter"));
await devtools.waitForSelector('[data-testid="props-row"]');

Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"esModuleInterop": true,
"target": "ES2020",
"strict": true,
Expand Down
Loading