Z ZPI 文档 v0.3.8
介绍页

源码

tests/desktop/file-preview.spec.ts

typescript269 行查看原文
import { mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import type { ElectronApplication } 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";

test("conversation files and folders share ZCode menus; folders open in Finder and files use the sidebar", async () => {
  const dir = await realpath(await mkdtemp(join(tmpdir(), "zpi-conversation-file-menu-")));
  const project = join(dir, "中文 项目");
  const folder = join(project, "资料 目录");
  await mkdir(folder, { recursive: true });
  const file = join(folder, "订单 %20 #1.json");
  await writeFile(file, '{"preview":"订单文件"}\n');
  const server = await fakeServer((_, response) => {
    send(
      response,
      chunk({
        content: [
          `[文件](<${pathToFileURL(file).href}#L1>)`,
          `[资料目录](<${folder}/>)`,
          "[相对目录](<./资料 目录/>)",
          `[文件 URL 目录](${pathToFileURL(folder).href})`,
        ].join("\n\n"),
      }),
    );
    done(response);
  });
  let app: ElectronApplication | undefined;
  try {
    app = await launchDesktop({ dir, url: server.url, project });
    const page = await app.firstWindow();
    await page.emulateMedia({ colorScheme: "light" });
    await app.evaluate(({ shell }) => {
      const calls: { action: string; path: string }[] = [];
      (globalThis as typeof globalThis & { fileMenuCalls: typeof calls }).fileMenuCalls = calls;
      shell.openPath = async (path) => {
        calls.push({ action: "open", path });
        return "";
      };
      shell.showItemInFolder = (path) => {
        calls.push({ action: "reveal", path });
      };
    });
    const calls = () =>
      app?.evaluate(() => (globalThis as typeof globalThis & { fileMenuCalls: unknown[] }).fileMenuCalls);
    const readClipboard = () => app?.evaluate(({ clipboard }) => clipboard.readText());
    const absoluteFolder = await realpath(folder);
    const absoluteFile = await realpath(file);
    await page.getByRole("button", { name: "添加项目", exact: true }).first().click();
    await expect(page.getByRole("button", { name: "选择项目", exact: true })).toHaveText("中文 项目");
    const editor = page.getByLabel("消息", { exact: true });
    await editor.fill(`检查 [订单](<${file}>)`);
    await editor.press("Enter");
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "completed");
    const answer = page.locator(".answer");
    const menu = page.getByRole("menu");
    for (const name of ["资料目录", "相对目录", "文件 URL 目录"])
      await answer.getByRole("button", { name, exact: true }).click();
    await expect
      .poll(calls)
      .toEqual(Array.from({ length: 3 }, () => ({ action: "open", path: absoluteFolder })));
    await expect(page.locator(".right-pane")).toBeHidden();
    await expect(page.getByRole("alert")).toHaveCount(0);

    const folderLink = answer.getByRole("button", { name: "资料目录", exact: true });
    await folderLink.click({ button: "right" });
    await expect(menu.getByRole("menuitem")).toHaveText([
      "打开",
      "Finder",
      "使用默认程序打开",
      "复制绝对路径",
      "复制相对路径",
    ]);
    await menu.getByRole("menuitem", { name: "Finder", exact: true }).click();
    await expect.poll(calls).toHaveLength(4);
    expect((await calls())?.at(-1)).toEqual({ action: "open", path: absoluteFolder });
    await folderLink.click({ button: "right" });
    await menu.getByRole("menuitem", { name: "复制相对路径", exact: true }).click();
    await expect.poll(readClipboard).toBe("资料 目录");
    await expect(page.locator(".right-pane")).toBeHidden();

    const fileLink = answer.getByRole("button", { name: "文件", exact: true });
    await fileLink.click({ button: "right" });
    await expect(menu.getByRole("menuitem")).toHaveText([
      "打开",
      "Finder",
      "使用默认程序打开",
      "复制绝对路径",
      "复制相对路径",
    ]);
    await expect(menu.getByRole("separator")).toHaveCount(2);
    await expect(menu).toHaveCSS("width", "208px");
    const finderIcon = menu.getByRole("menuitem", { name: "Finder", exact: true }).locator("img");
    await expect
      .poll(() => finderIcon.evaluate((img: HTMLImageElement) => img.naturalWidth))
      .toBeGreaterThan(0);
    await page.screenshot({ path: "test-results/conversation-file-menu-light.png" });
    await page.emulateMedia({ colorScheme: "dark" });
    await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
    await page.screenshot({ path: "test-results/conversation-file-menu-dark.png" });
    await menu.getByRole("menuitem", { name: "打开", exact: true }).click();
    await expect(page.getByRole("tab", { name: "订单 %20 #1.json", exact: true })).toBeVisible();
    await expect(page.locator(".file-text-preview")).toContainText("订单文件");
    await expect(page.locator(".file-text-preview")).toHaveAttribute("data-line", "1");
    await fileLink.click({ button: "right" });
    await menu.getByRole("menuitem", { name: "Finder", exact: true }).click();
    await expect.poll(calls).toHaveLength(5);
    expect((await calls())?.at(-1)).toEqual({ action: "reveal", path: absoluteFile });
    await fileLink.click({ button: "right" });
    await menu.getByRole("menuitem", { name: "复制绝对路径", exact: true }).click();
    await expect.poll(readClipboard).toBe(absoluteFile);
    await fileLink.click({ button: "right" });
    await menu.getByRole("menuitem", { name: "复制相对路径", exact: true }).click();
    await expect.poll(readClipboard).toBe("资料 目录/订单 %20 #1.json");

    await fileLink.click({ button: "right" });
    await expect(
      menu.getByRole("menuitem", { name: "使用默认程序打开", exact: true }).locator("svg"),
    ).toBeVisible();
    await menu.getByRole("menuitem", { name: "使用默认程序打开", exact: true }).click();
    await expect.poll(calls).toHaveLength(6);
    expect((await calls())?.at(-1)).toEqual({ action: "open", path: absoluteFile });

    const userFile = page.locator(".user-message-text").getByRole("button", { name: "订单", exact: true });
    await userFile.click({ button: "right" });
    await expect(menu.getByRole("menuitem")).toHaveCount(5);
    await menu.getByRole("menuitem", { name: "复制绝对路径", exact: true }).click();
    await expect.poll(readClipboard).toBe(absoluteFile);
    await rm(file);
    await fileLink.click({ button: "right" });
    await menu.getByRole("menuitem", { name: "Finder", exact: true }).click();
    await expect(page.getByRole("alert")).toContainText("找不到文件“订单 %20 #1.json”");
    expect(await calls()).toHaveLength(6);
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});

test("file references share icons and canonical text; chat files open reusable sidebar previews", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-file-references-"));
  const project = join(dir, "中文 项目");
  await mkdir(project);
  const config = join(project, "订单 汇总.json");
  await writeFile(config, '{"project":"ZPI references","orders":42}\n');
  const workbook = join(project, "模型数据.xlsx");
  await writeFile(workbook, await readFile(resolve("tests/fixtures/references.xlsx")));
  const skill = join(dir, ".agents", "skills", "review", "SKILL.md");
  await mkdir(join(dir, ".agents", "skills", "review"), { recursive: true });
  await writeFile(skill, "---\nname: review\ndescription: Review code\n---\nReview");
  const server = await fakeServer((_, response) => {
    send(response, chunk({ content: `[订单 汇总.json](<${config}>)\n\n[模型数据.xlsx](<${workbook}>)` }));
    done(response);
  });
  let app: ElectronApplication | undefined;
  try {
    app = await launchDesktop({ dir, url: server.url, project });
    const page = await app.firstWindow();
    const errors: string[] = [];
    page.on("pageerror", (error) => errors.push(error.message));
    await page.getByRole("button", { name: "添加项目", exact: true }).first().click();
    await expect(page.getByRole("button", { name: "选择项目", exact: true })).toHaveText("中文 项目");
    const editor = page.getByLabel("消息", { exact: true });
    await editor.fill("@订单");
    await expect(page.getByRole("option").locator("img")).toHaveAttribute(
      "src",
      /material-icons\/json\.svg$/,
    );
    await page.getByRole("option").click();
    const file = editor.locator(".inline-mention.file");
    await expect(file).toHaveText("订单 汇总.json");
    await expect(file).toHaveCSS("color", "rgb(26, 112, 184)");
    await file.click();
    await expect(page.getByRole("tab", { name: "订单 汇总.json", exact: true })).toBeVisible();
    await expect(page.locator(".file-text-preview")).toContainText("ZPI references");
    const canonical = `检查 [订单 汇总.json](<${config}>)\n/init\n/compact\n[$review](${skill})`;
    await editor.fill(canonical);
    await expect(editor.locator(".inline-mention.command")).toHaveText(["Init", "Compact"]);
    await expect(editor.locator(".inline-mention.skill")).toHaveCSS("color", "rgb(116, 83, 176)");
    await editor.press("Meta+A");
    const copied = await editor.evaluate((element) => {
      const data = new DataTransfer();
      element.dispatchEvent(
        new ClipboardEvent("copy", { clipboardData: data, bubbles: true, cancelable: true }),
      );
      return data.getData("text/plain");
    });
    expect(copied).toBe(canonical);
    await editor.press("ArrowRight");
    await page.screenshot({ path: "test-results/desktop-references-light.png" });
    await editor.press("Enter");
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "completed");
    await expect(page.locator(".user-message-text .inline-mention.command")).toHaveText(["Init", "Compact"]);
    const answer = page.locator(".answer");
    await answer.getByRole("button", { name: "订单 汇总.json", exact: true }).click();
    await expect(page.getByRole("tab", { name: "订单 汇总.json", exact: true })).toHaveCount(1);
    await answer.getByRole("button", { name: "模型数据.xlsx", exact: true }).click();
    await expect(page.getByRole("tab", { name: "模型数据.xlsx", exact: true })).toBeVisible();
    await expect(page.getByRole("tab", { name: "模型数据", exact: true })).toBeVisible();
    await expect(page.locator('[data-office-preview-kind="excel"] canvas').first()).toBeVisible();
    await expect(page.locator('[data-office-preview-kind="excel"] [role=alert]')).toHaveCount(0);
    await page.getByRole("tab", { name: "图表数据", exact: true }).click();
    await expect(page.getByRole("tab", { name: "图表数据", exact: true })).toHaveAttribute(
      "aria-selected",
      "true",
    );
    await page.screenshot({ path: "test-results/desktop-references-xlsx.png" });
    await page.emulateMedia({ colorScheme: "dark" });
    await expect(answer.locator(".inline-mention.file").first()).toHaveCSS("color", "rgb(128, 190, 255)");
    await page.screenshot({ path: "test-results/desktop-references-dark.png" });
    expect(errors).toEqual([]);
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});

test("embedded file images retain their preview across theme changes and reload on refresh", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-file-images-"));
  const project = join(dir, "project");
  await mkdir(project);
  const image = join(project, "image.svg");
  const svg = (width: number) =>
    `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="20"><rect width="100%" height="100%" fill="red"/></svg>`;
  await writeFile(image, svg(40));
  await writeFile(join(project, "README.md"), "# Preview\n\n![image](./image.svg)");
  const server = await fakeServer((_, response) => {
    send(response, chunk({ content: "[预览](./README.md)" }));
    done(response);
  });
  let app: ElectronApplication | undefined;
  try {
    app = await launchDesktop({ dir, project, url: server.url });
    const page = await app.firstWindow();
    await page.emulateMedia({ colorScheme: "light" });
    await page.getByRole("button", { name: "添加项目", exact: true }).first().click();
    await expect(page.getByRole("button", { name: "选择项目", exact: true })).toHaveText("project");
    const editor = page.getByLabel("消息", { exact: true });
    await editor.fill("预览图片");
    await editor.press("Enter");
    await page.locator(".answer").getByRole("button", { name: "预览", exact: true }).click();
    const rendered = page.locator(".file-markdown-preview img[data-streamdown=image]");
    const width = () => rendered.evaluate((element: HTMLImageElement) => element.naturalWidth);
    await expect.poll(width).toBe(40);
    await writeFile(image, svg(60));
    await page.emulateMedia({ colorScheme: "dark" });
    await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
    expect(await width()).toBe(40);
    await page.locator(".answer").getByRole("button", { name: "预览", exact: true }).click();
    await expect.poll(width).toBe(60);
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});