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
14 changes: 7 additions & 7 deletions apps/gpuix/tests/_shared/frame-pacing-native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const pump = async (): Promise<void> => {
};

try {
await pump();
renderer.activateWindow();
await pump();
assert.ok(scrollId, "scroll surface must be mounted");

Expand All @@ -77,7 +79,7 @@ try {
bounds.x + 100,
bounds.y + 80,
0,
commitEditor ? 8 : -8
commitEditor && event % 2 === 0 ? 8 : -8
);
}

Expand All @@ -99,12 +101,10 @@ try {
for (let burst = 0; burst < 4; burst++) {
const frames = renderer.getDebugFrameOverlayStats().frames;

renderer.simulateScrollWheel(
bounds.x + 100,
bounds.y + 80,
0,
commitEditor ? 8 : -8
);
// A textarea can start at either scroll boundary after following its caret.
for (const delta of commitEditor ? [8, -8] : [-8])
renderer.simulateScrollWheel(bounds.x + 100, bounds.y + 80, 0, delta);

await pump();
assert.ok(
renderer.getDebugFrameOverlayStats().frames > frames,
Expand Down
8 changes: 8 additions & 0 deletions apps/gpuix/tests/_shared/session-exit.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createTestRoot } from "@gpuix/react/testing";
import { TestGpuixRenderer } from "@gpuix/native";
import { App } from "../../src/app";
import { WorkspaceSessionStore } from "../../src/feature/workspace-tabs/lib/workspace-session-store";
import { repositorySnapshot } from "./repository-snapshot";
Expand Down Expand Up @@ -29,5 +30,12 @@ if (!message) throw new Error("Missing draft editor.");
root.renderer.focusElement(message.id);
root.renderer.simulateKeystrokes("cmd-a e x i t");
root.renderer.flush();
const dispose: unknown = Reflect.get(TestGpuixRenderer, "disposeCurrent");

if (typeof dispose !== "function")
throw new Error("Missing native test-session teardown patch.");

// Dispose the test window after the app's exit autosave, before Rust TLS teardown.
process.once("exit", () => dispose());
// GPUIX's normal window-close path calls process.exit; deliberately bypass the autosave timer and unmount.
process.exit(0);
31 changes: 16 additions & 15 deletions apps/gpuix/tests/_shared/vibrancy-native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ const applicationAppearance = send(
);

send(app, "setAppearance:", applicationAppearance);
const setWindowAppearance: unknown = Reflect.get(
renderer,
"setWindowAppearance"
);

assert.ok(
typeof setWindowAppearance === "function",
"native appearance patch must be present"
);
assert.throws(
() => Reflect.apply(setWindowAppearance, renderer, ["invalid"]),
/light or dark/
);
// App normally sets the window theme; this probe renders AppShell directly.
Reflect.apply(setWindowAppearance, renderer, [process.argv[2]]);
render(
<AppThemeContext
value={{
Expand Down Expand Up @@ -139,21 +154,7 @@ assert.equal(
process.argv[2] === "dark"
? "NSAppearanceNameVibrantDark"
: "NSAppearanceNameVibrantLight",
"the native backdrop must inherit appearance instead of pinning itself to light"
);

const setWindowAppearance: unknown = Reflect.get(
renderer,
"setWindowAppearance"
);

assert.ok(
typeof setWindowAppearance === "function",
"native appearance patch must be present"
);
assert.throws(
() => Reflect.apply(setWindowAppearance, renderer, ["invalid"]),
/light or dark/
"the native backdrop must inherit the selected window appearance"
);

for (const choice of ["dark", "light", "dark"] as const) {
Expand Down
33 changes: 25 additions & 8 deletions apps/gpuix/tests/packaging/launch.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* Covers: archive/helper integrity and foreground launch through macOS Launch Services.
* Real: extracted signed app, native frames, and GPUIX's stdio automation protocol.
* Real: extracted signed app, native tree/image, and GPUIX's stdio automation protocol.
* Doubles: fresh isolated session/accounts; no hosting or AI provider requests.
* Requires: Apple Silicon macOS graphics access; run root test:package:macos.
*/
Expand All @@ -15,6 +15,7 @@ import { closeSync, constants, openSync, writeSync } from "node:fs";
import { mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { resolve } from "node:path";
import sharp from "sharp";
import {
createSseDecoder,
encodeSse,
Expand Down Expand Up @@ -215,21 +216,37 @@ it("opens the extracted ZIP without a terminal or checkout-dependent resources",
expect(pid).toBeGreaterThan(1);

const deadline = Date.now() + 10000;
let painted: string[] = [];
let text: string[] = [];

while (!painted.includes("Welcome to 1git") && Date.now() < deadline) {
painted = methods.getPaintedText.result.parse(
await request("getPaintedText", {})
while (!text.includes("Welcome to 1git") && Date.now() < deadline) {
text = methods.getAllText.result.parse(
await request("getAllText", {})
).text;
await Bun.sleep(50);
}

expect(
painted,
text,
await Bun.file(resolve(directory, "stderr.log")).text()
).toContain("Welcome to 1git");
expect(painted).toContain("Continue");
expect(painted).not.toContain("Uncaught runtime errors:");
expect(text).toContain("Continue");
expect(text).not.toContain("Uncaught runtime errors:");
// The last-frame text log can be empty when GPUI reuses cached drawing.
const screenshot = resolve(directory, "launch.png");
let rendered = false;

do {
await request("screenshot", { path: screenshot });
const pixels = await sharp(screenshot).stats();

rendered = pixels.channels.slice(0, 3).some(({ stdev }) => stdev > 2);

if (!rendered) await Bun.sleep(50);
} while (!rendered && Date.now() < deadline);

expect(rendered, "The native window must render beyond a blank frame").toBe(
true
);
} finally {
// If initialization failed, locate only this test's unique extracted executable.
if (pid === undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ it("leaves the index and working files untouched if a streamed backup cannot be
await git("add", "a.txt");
await writeFile(join(root, "a.txt"), "working\n");
const originalIndex = await readFile(join(root, ".git/index"));
const filesystem = await import("node:fs/promises");
const filesystem = await import("@1git/git/runtime");
const originalOpen = filesystem.open;

spyOn(filesystem, "open").mockImplementation(async (...args) => {
Expand All @@ -194,6 +194,11 @@ it("leaves the index and working files untouched if a streamed backup cannot be
});
const result = await discard("a.txt");

expect(filesystem.open).toHaveBeenCalledWith(
join(root, ".git/recovery/working.patch"),
"wx",
0o600
);
expect(result.ok).toBe(false);
expect(await readFile(join(root, ".git/index"))).toEqual(originalIndex);
expect(await readFile(join(root, "a.txt"), "utf8")).toBe("working\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,9 @@ it.each(["publish", "push"] as const)(
expectedHead: head,
expectedBranch: "feature/ui",
}),
expect.any(AbortSignal)
expect.any(AbortSignal),
undefined,
expect.any(Function)
);
expect(browser).not.toHaveBeenCalled();
expect(root.renderer.getPaintedText().join(" ")).toContain(
Expand Down
8 changes: 7 additions & 1 deletion apps/gpuix/tests/repository-setup/setup.component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ for (const appearance of ["light", "dark"] as const) {
await settle();
click("welcome-view-list");
click("welcome-folder-other");
click("welcome-search");
root.renderer.nativeSimulateMouseMove(0, 0);
root.renderer.flush();
const action = () =>
root.renderer.findByTestId("welcome-forget-/test/project");

Expand Down Expand Up @@ -650,9 +653,12 @@ it("searches remembered repositories after closing all tabs and reopens without
root.renderer.flush();
click("welcome-view-list");
click("welcome-folder-other");
await settle();
const readsBeforeOpening = reader.mock.calls.length;

click("welcome-repository-/test/project");
await settle();
expect(reader).toHaveBeenCalledTimes(2);
expect(reader).toHaveBeenCalledTimes(readsBeforeOpening + 1);
expect(picker).not.toHaveBeenCalled();
expect(root.renderer.findByTestId("commit-composer")).toBeDefined();
});
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ it("decorates a clean working tree without disguising refresh errors as an empty
render("Refresh failed");
expect(root.renderer.findByTestId("empty-list-placeholder")).toBeUndefined();
expect(root.renderer.findByTestId("comparison-art-empty")).toBeUndefined();
expect(root.renderer.getAllText()).toContain("Could not refresh repository");
expect(root.renderer.getAllText()).toContain("Could not read repository");
});

beforeEach(() => {
Expand Down Expand Up @@ -266,7 +266,7 @@ it("pins the composer below the scrolling Changes list and hides it in revision
).toBeLessThanOrEqual(
bounds("repository-navigation").y + bounds("repository-navigation").height
);
expect(element("commit-message").style.height).toBe(100);
expect(bounds("commit-message").height).toBeGreaterThanOrEqual(100);
expect(root.renderer.getPaintedText()).toContain("Commit message…");
key("entry-src/app.tsx", "end");
expect(scrollY()).toBeLessThan(0);
Expand Down Expand Up @@ -497,7 +497,9 @@ const selectedView = () =>
(node) =>
node.aria?.role === "Tab" &&
node.aria.selected &&
["Changes", "History", "Stashes"].includes(node.aria.label ?? "")
["Changes", "History", "Stashes", "Graph"].includes(
node.aria.label ?? ""
)
)
.map((node) => node.aria?.label);

Expand All @@ -509,8 +511,8 @@ it("stacks compact borderless rail tabs outside the white workspace", () => {

expect(history.x).toBe(changes.x);
expect(stashes.x).toBe(changes.x);
expect(history.y).toBe(changes.y + changes.height + 6);
expect(stashes.y).toBe(history.y + history.height + 6);
expect(history.y).toBe(changes.y + changes.height + 4);
expect(stashes.y).toBe(history.y + history.height + 4);
expect(changes.x + changes.width).toBeLessThan(workspace.x);

for (const view of ["changes", "history", "stashes"]) {
Expand Down Expand Up @@ -561,12 +563,12 @@ it("tightens sidebar headers without shrinking rail buttons, file rows or checkb

it("wraps vertical rail arrows, supports Home/End and Enter/Space, and ignores modified navigation", () => {
key("view-changes", "up");
expect(selectedView()).toEqual(["Stashes"]);
key("view-stashes", "down");
expect(selectedView()).toEqual(["Graph"]);
key("view-graph", "down");
expect(selectedView()).toEqual(["Changes"]);
key("view-changes", "end");
expect(selectedView()).toEqual(["Stashes"]);
key("view-stashes", "home");
expect(selectedView()).toEqual(["Graph"]);
key("view-graph", "home");
expect(selectedView()).toEqual(["Changes"]);
key("view-changes", "shift-right");
expect(selectedView()).toEqual(["Changes"]);
Expand Down Expand Up @@ -819,6 +821,11 @@ it("supports arrow-key rail selection and entry/exit with Tab", () => {
root.renderer.simulateKeystrokes("tab");
root.renderer.flush();
notifyListFocus();
expect(root.renderer.getFocusedElementId()).toBe(
element("repository-list-scrollbar-vertical").id
);
root.renderer.simulateKeystrokes("tab");
root.renderer.flush();
expect(root.renderer.getFocusedElementId()).toBe(
element("workspace-home").id
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ it.each([
["light", "#a478ef"],
["dark", "#a478ef"],
] as const)(
"keeps native blur over %s application appearance with repo color %s",
"keeps native blur over %s window appearance with repo color %s",
(appearance, color) => {
const output = execFileSync(
process.execPath,
Expand Down
6 changes: 3 additions & 3 deletions apps/gpuix/tests/workspace-session/restore.component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ it("keeps an unavailable repository and editable draft, then retries its saved p
<App initialSession={initial} readRepository={reader} saveSession={save} />
);
await settle();
expect(root.renderer.getAllText()).toContain("Repository unavailable");
expect(root.renderer.getAllText()).toContain("/test/project");
expect(root.renderer.getAllText()).toContain("Could not read repository");
expect(reader).toHaveBeenCalledWith("/test/project", expect.any(AbortSignal));
expect(node("commit-message").customProps?.value).toBe("Main draft");
await Bun.sleep(240);
root.renderer.flush();
Expand All @@ -224,7 +224,7 @@ it("keeps an unavailable repository and editable draft, then retries its saved p
await settle();
expect(reader.mock.calls[1]?.[0]).toBe("/test/project");
expect(node("commit-message").customProps?.value).toBe("recovered");
expect(root.renderer.getAllText()).not.toContain("Repository unavailable");
expect(root.renderer.getAllText()).not.toContain("Could not read repository");
root.render(<div />);
expect(save.mock.calls.at(-1)?.[0].tabs[0]).toMatchObject({
path: "/test/project",
Expand Down
15 changes: 11 additions & 4 deletions apps/gpuix/tests/workspace-tabs/home.component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,14 @@ function tabCount() {
return Object.values(root.renderer.getA11yTree().nodes ?? {}).filter(
(item) =>
item.aria?.role === "Tab" &&
!["Changes", "History", "Stashes", "Tile view", "List view"].includes(
item.aria.label ?? ""
)
![
"Changes",
"History",
"Stashes",
"Graph",
"Tile view",
"List view",
].includes(item.aria.label ?? "")
).length;
}

Expand Down Expand Up @@ -284,7 +289,9 @@ it("preserves tabs, drafts, checked files and selection and reuses an already-op
expect(node("include-a.ts").customProps?.["aria-checked"]).toBe(false);
expect(node("entry-a.ts").customProps?.["aria-selected"]).toBe(true);
expect(tabCount()).toBe(2);
expect(read).toHaveBeenCalledTimes(2);
expect(
read.mock.calls.filter(([path]) => path === "/test/project")
).toHaveLength(1);
click("workspace-home");
click("welcome-repository-/test/third");
await settle();
Expand Down
Loading