Z ZPI 文档 v0.3.8
介绍页

源码

tests/desktop/workspace-appearance.spec.ts

typescript248 行查看原文
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { ElectronApplication, Locator } from "@playwright/test";
import { chunk, done, fakeServer, send } from "../fake-server.ts";
import { runCleanup } from "../helpers/cleanup.ts";
import { launchDesktop } from "../helpers/desktop.ts";
import { expect, test } from "../helpers/test.ts";

async function bounds(locator: Locator) {
  const box = await locator.boundingBox();
  if (!box) throw new Error("Expected a visible panel");
  return box;
}

test("drafts and completed tasks retain ZCode's four corner insets with both sidebars collapsed", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-window-corners-"));
  const server = await fakeServer((_, response) => {
    send(response, chunk({ content: "完成\n\n```text\nZCode theme\n```" }));
    done(response);
  });
  let app: Awaited<ReturnType<typeof launchDesktop>>;
  try {
    app = await launchDesktop({ dir, url: server.url });
    const page = await app.firstWindow();
    await page.getByLabel("收起侧边栏", { exact: true }).click();
    await expect(page.locator(".sidebar")).toBeHidden();
    const main = page.locator(".workspace-surface > main");
    const radius = await page.evaluate(() => window.zpi.workspacePanelRadius);
    for (const state of ["draft", "task"] as const) {
      if (state === "task") {
        await main.getByLabel("消息", { exact: true }).fill("检查窗口四角");
        await main.getByLabel("发送", { exact: true }).click();
        await expect(main.getByTestId("run")).toHaveAttribute("data-status", "completed");
      }
      for (const size of [
        [1200, 800],
        [900, 640],
      ]) {
        await app.evaluate(
          ({ BrowserWindow }, size) => BrowserWindow.getAllWindows()[0].setSize(size[0], size[1]),
          size,
        );
        for (const colorScheme of ["light", "dark"] as const) {
          await page.emulateMedia({ colorScheme });
          await expect(page.locator(".composer")).toHaveCSS(
            "background-color",
            colorScheme === "light" ? "rgb(255, 255, 255)" : "rgb(43, 43, 43)",
          );
          if (state === "task") {
            // ZCode message.tsx gives fenced code the card surface, independent of the panel surface.
            await expect(main.locator(".markdown-code")).toHaveCSS(
              "background-color",
              colorScheme === "light" ? "rgb(255, 255, 255)" : "rgb(43, 43, 43)",
            );
          }
          await expect
            .poll(() =>
              main.evaluate((element) => {
                const box = element.getBoundingClientRect();
                return [box.left, box.top, innerWidth - box.right, innerHeight - box.bottom];
              }),
            )
            .toEqual([4, 4, 4, 4]);
          await expect(main).toHaveCSS("border-radius", `${radius}px`);
          await expect(main).toHaveCSS("overflow", "hidden");
          await page.screenshot({
            path: `test-results/window-corners-${state}-${size[0]}-${colorScheme}.png`,
          });
        }
      }
    }
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});

test("workspace has independent rounded panels, transparent gutters and ZCode launcher fills", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-workspace-appearance-"));
  let app: ElectronApplication | undefined;
  try {
    app = await launchDesktop({ dir, url: "" });
    const page = await app.firstWindow();
    await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0]?.setSize(1440, 900));
    await page.emulateMedia({ colorScheme: "light" });
    await page.getByLabel("新建任务", { exact: true }).first().click();
    await expect(page.locator(".composer-stack.with-header")).toHaveCSS(
      "box-shadow",
      "rgba(0, 0, 0, 0.05) 0px 20px 25px -5px, rgba(0, 0, 0, 0.05) 0px 8px 10px -6px",
    );
    await page.getByLabel("展开右侧栏", { exact: true }).click();
    await expect(page.locator(".right-pane")).not.toHaveAttribute("data-animating", "true");
    const viewport = await page.evaluate(() => ({ width: innerWidth, height: innerHeight }));
    const main = page.locator(".workspace-surface > main");
    const frame = page.locator(".right-pane-frame");
    const leftHandle = page.getByRole("separator", { name: "侧边栏宽度", exact: true });
    const rightHandle = page.getByRole("separator", { name: "右侧栏宽度", exact: true });
    const launcher = page.locator(".pane-empty-launcher");
    const mac = await app.evaluate(() => process.platform === "darwin");
    await expect
      .poll(() =>
        page
          .locator(".sidebar")
          .evaluate(
            (el) =>
              el.getBoundingClientRect().width -
              Number.parseFloat(getComputedStyle(el).getPropertyValue("--sidebar-content-width")),
          ),
      )
      .toBe(0);
    const newTask = page.locator(".sidebar-global > button");
    const newTaskBounds = await bounds(newTask);
    expect(newTaskBounds.x).toBe(8);
    expect(newTaskBounds.y).toBe(mac ? 60 : 56);
    await expect(page.locator(".sidebar")).toHaveCSS("transition-duration", "0.2s, 0.2s, 0.2s");
    await expect(page.locator(".right-pane")).toHaveCSS("transition-duration", "0s");
    await expect(page.locator(".composer")).toHaveCSS("transition-duration", "0.15s, 0.15s");
    const panelRadius = await app.evaluate(() =>
      process.platform === "darwin" && Number.parseInt(process.getSystemVersion(), 10) < 26 ? 6 : 12,
    );
    if (mac) {
      // Electron's getter normalizes the transparent native background to RGB.
      expect(
        await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0]?.getBackgroundColor()),
      ).toBe("#000000");
      await expect(page.locator("html")).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
    }
    // Reference: ZCode DesktopWindowFrame and theme-zai light/dark tokens.
    async function expectBackdrop(dark = false) {
      const expected = await page.evaluate(
        ({ mac, dark }) => {
          const ref = document.createElement("div");
          ref.style.backgroundColor = mac
            ? `color-mix(in oklab, ${dark ? "#2b2b2b 60%" : "#f8f8f8 70%"}, transparent)`
            : dark
              ? "#2b2b2b"
              : "#ececee";
          document.body.append(ref);
          const color = getComputedStyle(ref).backgroundColor;
          ref.remove();
          return color;
        },
        { mac, dark },
      );
      await expect(page.locator(".shell")).toHaveCSS("background-color", expected);
    }
    await expectBackdrop();
    await expect(page.locator(".sidebar")).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
    for (const panel of [main, frame]) {
      await expect(panel).toHaveCSS("background-color", "rgb(248, 248, 248)");
      await expect(panel).toHaveCSS("border-radius", `${panelRadius}px`);
      await expect(panel).toHaveCSS("overflow", "hidden");
      const box = await bounds(panel);
      expect(box?.y).toBe(4);
      expect(box?.height).toBe(viewport.height - 8);
    }
    const sidebarBox = await bounds(page.locator(".sidebar"));
    const mainBox = await bounds(main);
    const frameBox = await bounds(frame);
    expect(mainBox.x - sidebarBox.width).toBe(4);
    expect(frameBox.x - mainBox.x - mainBox.width).toBe(4);
    expect(frameBox.x + frameBox.width).toBe(viewport.width - 4);
    for (const handle of [leftHandle, rightHandle]) {
      await expect(handle).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
      await expect(handle).toHaveCSS("width", "4px");
      await handle.hover();
      await expect.poll(() => handle.evaluate((el) => getComputedStyle(el, "::after").opacity)).toBe("1");
    }
    for (const name of ["文件", "浏览器", "终端", "审查"]) {
      await expect(launcher.getByRole("button", { name, exact: true })).toHaveCSS(
        "background-color",
        "rgba(0, 0, 0, 0)",
      );
    }
    await expect(launcher.locator("h2")).toHaveCSS("font-size", "18px");
    await expect(launcher.locator(".pane-launcher-actions")).toHaveCSS("display", "grid");
    await expect(launcher.getByRole("button", { name: "浏览器", exact: true })).toHaveCSS("height", "68px");
    await page.mouse.move(0, 0);
    await expect.poll(() => rightHandle.evaluate((el) => getComputedStyle(el, "::after").opacity)).toBe("0");
    await page.screenshot({ path: "test-results/workspace-rounded-panels.png" });
    const initialWidth = sidebarBox.width;
    await leftHandle.press("ArrowRight");
    await expect.poll(async () => (await bounds(page.locator(".sidebar")))?.width).toBe(initialWidth + 16);
    await rightHandle.press("ArrowLeft");
    await expect.poll(async () => (await bounds(frame))?.width).toBeGreaterThan(frameBox.width);
    await page.getByLabel("收起侧边栏", { exact: true }).click();
    await expect.poll(async () => (await bounds(main))?.x).toBe(4);
    await page.getByLabel("收起右侧栏", { exact: true }).click();
    await expect(frame).toBeHidden();
    await expect
      .poll(async () => {
        const box = await bounds(main);
        return box.x + box.width;
      })
      .toBe(viewport.width - 4);
    await expect(main).toHaveCSS("border-radius", `${panelRadius}px`);
    const inset = await bounds(main);
    expect([
      inset.x,
      inset.y,
      viewport.width - inset.x - inset.width,
      viewport.height - inset.y - inset.height,
    ]).toEqual([4, 4, 4, 4]);
    await page.screenshot({ path: "test-results/workspace-collapsed-four-corners.png" });
    await page.getByLabel("展开右侧栏", { exact: true }).click();
    await page.emulateMedia({ colorScheme: "dark" });
    await expectBackdrop(true);
    await page.getByLabel("展开侧边栏", { exact: true }).click();
    // The sidebar must expose DesktopWindowFrame's gray backdrop, including after collapse/expand.
    await expect(page.locator(".sidebar")).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
    await expect(main).toHaveCSS("background-color", "rgb(22, 22, 22)");
    await expect(frame).toHaveCSS("background-color", "rgb(22, 22, 22)");
    const plus = page.locator(".composer-plus");
    await plus.hover();
    await expect(plus).toHaveCSS("background-color", "rgba(255, 255, 255, 0.05)");
    await page.locator(".model-trigger").click();
    const menu = page.locator(".model-menu");
    await expect(menu).toHaveCSS("background-color", "rgb(43, 43, 43)");
    const item = menu.getByRole("menuitem").first();
    await item.hover();
    await expect(item).toHaveCSS("background-color", "rgb(54, 54, 54)");
    await page.screenshot({ path: "test-results/workspace-dark-menu.png" });
    await page.keyboard.press("Escape");
    await page.getByLabel("收起侧边栏", { exact: true }).click();
    await expect(launcher.getByRole("button", { name: "浏览器", exact: true })).toHaveCSS(
      "background-color",
      "rgba(0, 0, 0, 0)",
    );
    await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0]?.setSize(900, 640));
    await expect
      .poll(async () => {
        const box = await bounds(frame);
        const viewport = await page.evaluate(() => ({ width: innerWidth, height: innerHeight }));
        return [box.y, box.height - viewport.height, box.x + box.width - viewport.width];
      })
      .toEqual([4, -8, -4]);
    await expect(launcher.locator(".pane-launcher-actions")).toHaveCSS("display", "grid");
    await expect(launcher.getByRole("button", { name: "浏览器", exact: true })).toHaveCSS("height", "68px");
    await page.screenshot({ path: "test-results/workspace-rounded-panels-narrow-dark.png" });
  } finally {
    await runCleanup([() => rm(dir, { recursive: true, force: true }), () => app?.close()]);
  }
});