Z ZPI 文档 v0.3.8
介绍页

源码

tests/desktop/default-editor-open.spec.ts

typescript124 行查看原文
import { chmod, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
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("default opening uses the associated IDE for Java links and Java/Markdown previews", async () => {
  test.skip(process.platform !== "darwin", "macOS editor launchers");
  const dir = await realpath(await mkdtemp(join(tmpdir(), "zpi-default-editor-")));
  const project = join(dir, "project");
  const bundle = join(dir, "IntelliJ IDEA.app");
  const binary = join(bundle, "Contents", "MacOS", "idea");
  const log = join(dir, "editor-args.txt");
  await mkdir(project);
  await mkdir(join(bundle, "Contents", "MacOS"), { recursive: true });
  await writeFile(binary, `#!/bin/sh\nprintf '%s\\n' "$@" > '${log}'\n`);
  await chmod(binary, 0o755);
  const filename = "订单 %20 #1.java";
  const path = join(project, filename);
  await writeFile(path, Array.from({ length: 120 }, (_, i) => `// line ${i + 1}`).join("\n"));
  const markdownFilename = "说明 %20 #1.md";
  const markdownPath = join(project, markdownFilename);
  await writeFile(markdownPath, "# Default opening\n");
  const server = await fakeServer((_, response) => {
    send(
      response,
      chunk({
        content: `[Java 源码](<./${encodeURIComponent(filename)}:80:3>) [Markdown](<./${encodeURIComponent(markdownFilename)}>)`,
      }),
    );
    done(response);
  });
  let app: ElectronApplication | undefined;
  try {
    app = await launchDesktop({ dir, project, url: server.url });
    await app.evaluate(({ app, nativeImage, shell }, bundle) => {
      app.getApplicationInfoForProtocol = async () => ({
        path: bundle,
        name: "IntelliJ IDEA",
        icon: nativeImage.createEmpty(),
      });
      const paths: string[] = [];
      Object.assign(globalThis, { defaultEditorFallbackPaths: paths });
      shell.openPath = async (path) => {
        paths.push(path);
        return "";
      };
    }, bundle);
    const page = await app.firstWindow();
    await page.evaluate(() => localStorage.setItem("zpi-last-editor-id", "vscode"));
    await page.getByRole("button", { name: "添加项目", exact: true }).first().click();
    await page.getByLabel("消息", { exact: true }).fill("打开 Java 文件");
    await page.getByLabel("消息", { exact: true }).press("Enter");
    await expect(page.getByTestId("run")).toHaveAttribute("data-status", "completed");
    const link = page.locator(".answer").getByRole("button", { name: "Java 源码", exact: true });
    await link.click({ button: "right" });
    await page.getByRole("menuitem", { name: "使用默认程序打开", exact: true }).click();
    await expect
      .poll(async () => (await readFile(log, "utf8").catch(() => "")).trim().split("\n"))
      .toEqual(["--line", "80", path]);
    for (const [name, expectedPath] of [
      ["Java 源码", path],
      ["Markdown", markdownPath],
    ]) {
      await page.locator(".answer").getByRole("button", { name, exact: true }).click();
      await expect(page.locator(".file-manager-toolbar:visible .preview-breadcrumb")).toHaveAttribute(
        "title",
        expectedPath,
      );
      await page
        .locator(".file-manager-toolbar:visible")
        .getByRole("button", { name: "使用默认程序打开", exact: true })
        .click();
      await expect.poll(async () => (await readFile(log, "utf8")).trim()).toBe(expectedPath);
    }
    const perform = (line?: number) =>
      page.evaluate(
        async ({ path, line }) => {
          const sessions = await window.zpi.listRecentSessions();
          if (!sessions.ok) throw new Error(sessions.error.message);
          return window.zpi.fileAction(sessions.value[0].id, path, "open", line ? { line } : undefined);
        },
        { path, line },
      );
    const result = await perform();
    expect(result.ok).toBe(true);
    await expect.poll(async () => (await readFile(log, "utf8")).trim()).toBe(path);
    const fallbackCalls = () =>
      app?.evaluate(
        () =>
          (globalThis as typeof globalThis & { defaultEditorFallbackPaths: string[] })
            .defaultEditorFallbackPaths,
      );
    expect(await fallbackCalls()).toEqual([]);
    for (const [index, mode] of ["not-executable", "exit-error", "missing"].entries()) {
      if (mode === "not-executable") await chmod(binary, 0o600);
      else if (mode === "exit-error") {
        await writeFile(binary, "#!/bin/sh\nexit 7\n");
        await chmod(binary, 0o755);
      } else await rm(binary);
      const fallback = await perform(80);
      expect(fallback.ok).toBe(true);
      expect(await fallbackCalls()).toEqual(Array(index + 1).fill(path));
    }
    await app.evaluate(({ shell }) => {
      shell.openPath = async () => "没有可用的默认应用程序";
    });
    const failed = await perform();
    expect(failed.ok).toBe(false);
    if (failed.ok) throw new Error("Expected both open methods to fail");
    expect(failed.error.message).toContain("ENOENT");
    expect(failed.error.message).toContain("没有可用的默认应用程序");
  } finally {
    await runCleanup([
      () => rm(dir, { recursive: true, force: true }),
      () => server.close(),
      () => app?.close(),
    ]);
  }
});