Z ZPI 文档 v0.3.8
介绍页

源码

tests/desktop/conversation-interactions.spec.ts

typescript260 行查看原文
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { chunk, deferred, 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("long user messages match ZCode collapse and preserve copying and find navigation", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-user-collapse-"));
  const server = await fakeServer((_, response) => {
    send(response, chunk({ content: "完成。" }));
    done(response);
  });
  let app: Awaited<ReturnType<typeof launchDesktop>>;
  try {
    app = await launchDesktop({ dir, url: server.url });
    const page = await app.firstWindow();
    const text = `${"检查长消息的折叠和展开。\n".repeat(60)}末尾定位词`;
    const editor = page.getByLabel("消息", { exact: true });
    await editor.fill(text);
    await page.getByLabel("发送", { exact: true }).click();
    const run = page.getByTestId("run").first();
    const body = run.locator(".user-message-body");
    const content = body.locator(".user-message-content");
    await expect(run).toHaveAttribute("data-status", "completed");
    await expect(body).toHaveAttribute("data-expandable", "true");
    await expect(content).toHaveCSS("max-height", "120px");
    await expect(content).toHaveCSS("mask-image", /linear-gradient/);
    await body.getByRole("button", { name: "展开", exact: true }).click();
    await expect(body).toHaveAttribute("data-expanded", "true");
    await expect.poll(() => content.evaluate((el) => el.clientHeight)).toBeGreaterThan(120);
    await expect(content).toHaveCSS("mask-image", "none");
    const collapse = body.getByRole("button", { name: "收起", exact: true });
    await collapse.focus();
    await collapse.press("Enter");
    await expect(content).toHaveCSS("max-height", "120px");
    await run.locator(".user-message-row").hover();
    await run.locator(".user-message-actions").getByLabel("复制", { exact: true }).click();
    expect(await app.evaluate(({ clipboard }) => clipboard.readText())).toBe(text);
    await page.screenshot({ path: "test-results/user-message-collapsed.png" });
    await editor.focus();
    await editor.press("Meta+f");
    const find = page.getByRole("dialog", { name: "在任务中查找", exact: true });
    await find.getByRole("textbox").fill("末尾定位词");
    await expect(find.getByRole("status")).toHaveText("1/1");
    await expect(body).toHaveAttribute("data-expanded", "true");
    await expect
      .poll(() =>
        page.evaluate(() => {
          const range = [...(CSS.highlights.get("zpi-conversation-find-active") ?? [])][0];
          const viewport = document.querySelector(".conversation")?.getBoundingClientRect();
          if (!(range instanceof Range) || !viewport) return false;
          const hit = range.getBoundingClientRect();
          return hit.top >= viewport.top && hit.bottom <= viewport.bottom;
        }),
      )
      .toBe(true);
    await find.getByRole("button", { name: "关闭查找", exact: true }).click();
    await editor.fill("短消息");
    await page.getByLabel("发送", { exact: true }).click();
    await expect(page.getByTestId("run").last()).toHaveAttribute("data-status", "completed");
    await expect(page.getByTestId("run").last().locator(".user-message-toggle")).toHaveCount(0);
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});

test("side chats reuse the smallest free number and retain other conversations", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-side-chat-number-"));
  const server = await fakeServer((_, response) => {
    send(response, chunk({ content: "完成。" }));
    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 }).fill("主对话");
    await page.getByLabel("发送", { exact: true }).click();
    await expect(page.getByTestId("run")).toHaveAttribute("data-status", "completed");
    await page.getByLabel("展开右侧栏", { exact: true }).click();
    await page.locator(".pane-empty-launcher").getByRole("button", { name: "辅助对话", exact: true }).click();
    const add = async () => {
      await page.getByLabel("新增侧栏标签", { exact: true }).click();
      await page
        .locator(".pane-empty-launcher:visible")
        .getByRole("button", { name: "辅助对话", exact: true })
        .click();
    };
    await expect(page.getByRole("tab", { name: "辅助对话 1", exact: true })).toBeVisible();
    await add();
    await expect(page.getByRole("tab", { name: "辅助对话 2", exact: true })).toBeVisible();
    await add();
    const third = page.getByRole("region", { name: "辅助对话", exact: true });
    await expect(page.getByRole("tab", { name: "辅助对话 3", exact: true })).toBeVisible();
    await third.getByLabel("消息", { exact: true }).fill("保留第三份草稿");
    await page.getByRole("tab", { name: "辅助对话 2", exact: true }).hover();
    await page.getByLabel("关闭标签 辅助对话 2", { exact: true }).click();
    await expect(page.getByRole("tab", { name: "辅助对话 2", exact: true })).toHaveCount(0);
    await add();
    await expect(page.getByRole("tab", { name: "辅助对话 2", exact: true })).toBeVisible();
    await expect(page.getByRole("tab", { name: "辅助对话 4", exact: true })).toHaveCount(0);
    await page
      .getByRole("tab", { name: "辅助对话 3", exact: true })
      .getByRole("button", { name: "辅助对话 3", exact: true })
      .click();
    await expect(third.getByLabel("消息", { exact: true })).toHaveText("保留第三份草稿");
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});

test("side chat submits through the composer without renderer-only fields", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-side-chat-send-"));
  const server = await fakeServer((_, response) => {
    send(response, chunk({ content: "辅助回复" }));
    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 }).fill("主对话");
    await page.getByLabel("发送", { exact: true }).click();
    await expect(page.getByTestId("run")).toHaveAttribute("data-status", "completed");
    await page.getByLabel("展开右侧栏", { exact: true }).click();
    await page.locator(".pane-empty-launcher").getByRole("button", { name: "辅助对话", exact: true }).click();
    const side = page.getByRole("region", { name: "辅助对话", exact: true });
    const editor = side.getByLabel("消息", { exact: true });
    await editor.fill("辅助对话消息");
    await editor.press("Enter");
    await expect
      .poll(() => server.requests.some((body) => JSON.stringify(body).includes("辅助对话消息")))
      .toBe(true);
    await expect(side.getByTestId("run").last()).toHaveAttribute("data-status", "completed");
    await expect(side.locator(".answer").last()).toContainText("辅助回复");
    await expect(side.locator(".input-notice")).toHaveCount(0);
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});

test("clicking inside the current task clears a completion dot and persists the acknowledgement", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-read-current-task-"));
  const finish = deferred();
  const server = await fakeServer(async (_, response) => {
    send(response, chunk({ content: "任务结果" }));
    await finish.promise;
    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 }).fill("清除当前任务的蓝点");
    await page.getByLabel("发送", { exact: true }).click();
    await expect(page.locator(".answer")).toContainText("任务结果");
    const id = await page.evaluate(async () => {
      const id = localStorage.getItem("zpi.selectedSession");
      const other = await window.zpi.createSession(null);
      if (!id || !other.ok) throw new Error("Missing task");
      // Keep the renderer on the first task while host focus points elsewhere.
      await window.zpi.activateSession(other.value.id);
      return id;
    });
    finish.resolve();
    await expect(page.getByTestId("run")).toHaveAttribute("data-status", "completed");
    const dot = page.locator(`[data-session-id="${id}"] [data-unread-indicator]`);
    await expect(dot).toBeVisible();
    await page.locator(".answer").click();
    await expect(dot).toHaveCount(0);
    const unread = await page.evaluate(async (id) => {
      const snapshot = await window.zpi.getSessionSnapshot(id);
      if (!snapshot.ok) throw new Error(snapshot.error.message);
      return snapshot.value.session.unreadAt;
    }, id);
    expect(unread).toBeUndefined();
    await page.reload();
    await expect(page.getByTestId("run")).toHaveAttribute("data-status", "completed");
    await expect(dot).toHaveCount(0);
  } finally {
    finish.resolve();
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});

test("an absolute HTML link previews the streamed write before the file exists and opens after completion", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-pending-html-"));
  const path = join(dir, "zpi-stream-stall-explainer.html");
  const source = "<!doctype html><html><title>写入中的预览</title><body>生成中的页面</body></html>";
  const finish = deferred();
  const server = await fakeServer(async (_, response, index) => {
    if (index === 0) {
      send(
        response,
        chunk({
          tool_calls: [
            {
              index: 0,
              id: "write-html",
              type: "function",
              function: { name: "write", arguments: JSON.stringify({ path, content: source }).slice(0, -2) },
            },
          ],
        }),
      );
      await finish.promise;
      send(response, chunk({ tool_calls: [{ index: 0, function: { arguments: '"}' } }] }));
      done(response, "tool_calls");
    } else {
      send(response, chunk({ content: "完成。" }));
      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 }).fill("生成 HTML 页面");
    await page.getByLabel("发送", { exact: true }).click();
    const chip = page.locator('[data-tool-name="write"] .tool-file-chip');
    await expect(chip).toContainText("zpi-stream-stall-explainer.html");
    await expect(readFile(path)).rejects.toThrow("ENOENT");
    await chip.click();
    await expect(page.locator(".pending-file-preview")).toContainText(source);
    await expect(page.locator(".notice-window")).toHaveCount(0);
    finish.resolve();
    await expect(page.getByTestId("run")).toHaveAttribute("data-status", "completed");
    await expect(page.locator(".pending-file-preview")).toHaveCount(0);
    expect(await readFile(path, "utf8")).toBe(source);
    await page.locator(".changes-pane").getByRole("button", { name: "查看源码", exact: true }).click();
    await expect(page.locator(".file-text-preview")).toContainText(source);
    await expect(page.locator(".notice-window")).toHaveCount(0);
  } finally {
    finish.resolve();
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});