opencode-usage-doc.test.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * @vitest-environment happy-dom
  3. */
  4. import fs from "node:fs";
  5. import path from "node:path";
  6. import type { ReactNode } from "react";
  7. import { act } from "react";
  8. import { createRoot } from "react-dom/client";
  9. import { NextIntlClientProvider } from "next-intl";
  10. import { describe, expect, test, vi } from "vitest";
  11. import { UsageDocContent } from "@/app/[locale]/usage-doc/page";
  12. import { locales } from "@/i18n/config";
  13. // 测试环境不加载 next-intl/navigation -> next/navigation 的真实实现(避免 Next.js 运行时依赖)
  14. vi.mock("@/i18n/routing", () => ({
  15. Link: ({ children }: { children: ReactNode }) => children,
  16. }));
  17. function loadUsageMessages(locale: string) {
  18. return JSON.parse(
  19. fs.readFileSync(path.join(process.cwd(), "messages", locale, "usage.json"), "utf8")
  20. );
  21. }
  22. function renderWithIntl(locale: string, node: ReactNode) {
  23. const container = document.createElement("div");
  24. document.body.appendChild(container);
  25. const root = createRoot(container);
  26. const usageMessages = loadUsageMessages(locale);
  27. act(() => {
  28. root.render(
  29. <NextIntlClientProvider locale={locale} messages={{ usage: usageMessages }} timeZone="UTC">
  30. {node}
  31. </NextIntlClientProvider>
  32. );
  33. });
  34. return {
  35. unmount: () => {
  36. act(() => root.unmount());
  37. container.remove();
  38. },
  39. };
  40. }
  41. describe("UsageDoc - OpenCode 配置教程", () => {
  42. test("OpenCode 段落应位于 Gemini CLI 之后、Droid 之前", () => {
  43. const { unmount } = renderWithIntl("en", <UsageDocContent origin="http://localhost:23000" />);
  44. const h2Ids = Array.from(document.querySelectorAll("h2")).map((el) => el.id);
  45. expect(h2Ids).toContain("gemini");
  46. expect(h2Ids).toContain("opencode");
  47. expect(h2Ids).toContain("droid");
  48. expect(h2Ids.indexOf("gemini")).toBeLessThan(h2Ids.indexOf("opencode"));
  49. expect(h2Ids.indexOf("opencode")).toBeLessThan(h2Ids.indexOf("droid"));
  50. unmount();
  51. });
  52. test("应提供单份 opencode.json 示例,且包含 cch 端点与所有要求模型", () => {
  53. const { unmount } = renderWithIntl("en", <UsageDocContent origin="http://localhost:23000" />);
  54. const text = document.body.textContent || "";
  55. expect(text).toContain('"$schema": "https://opencode.ai/config.json"');
  56. expect(text).toContain('"baseURL": "http://localhost:23000/v1"');
  57. expect(text).toContain('"npm": "@ai-sdk/anthropic"');
  58. expect(text).toContain('"npm": "@ai-sdk/google"');
  59. expect(text).toContain('"npm": "@ai-sdk/openai"');
  60. expect(text).not.toContain("@ai-sdk/openai-compatible");
  61. expect(text).toContain("claude-haiku-4-5-20251001");
  62. expect(text).toContain("claude-sonnet-4-5-20250929");
  63. expect(text).toContain("claude-opus-4-5-20251101");
  64. expect(text).toContain('"model": "openai/gpt-5.2"');
  65. expect(text).toContain('"small_model": "openai/gpt-5.2-small"');
  66. expect(text).toContain("gpt-5.2");
  67. expect(text).toContain("gpt-5.2-small");
  68. expect(text).toContain('"reasoningEffort": "xhigh"');
  69. expect(text).toContain('"reasoningEffort": "medium"');
  70. expect(text).toContain('"store": false');
  71. expect(text).toContain('"setCacheKey": true');
  72. expect(text).toContain("reasoning.encrypted_content");
  73. expect(text).toContain("gemini-3-pro-preview");
  74. expect(text).toContain("gemini-3-flash-preview");
  75. expect(text).toContain('"baseURL": "http://localhost:23000/v1beta"');
  76. unmount();
  77. });
  78. test("应包含官方安装方式示例(curl/npm/bun/brew/paru,以及 Windows 包管理器)", () => {
  79. const { unmount } = renderWithIntl("en", <UsageDocContent origin="http://localhost:23000" />);
  80. const text = document.body.textContent || "";
  81. expect(text).toContain("curl -fsSL https://opencode.ai/install | bash");
  82. expect(text).toContain("npm install -g opencode-ai");
  83. expect(text).toContain("npm mirror registries");
  84. expect(text).toContain("bun add -g opencode-ai");
  85. expect(text).toContain("brew install anomalyco/tap/opencode");
  86. expect(text).toContain("paru -S opencode-bin");
  87. expect(text).toContain("choco install opencode");
  88. expect(text).toContain("scoop bucket add extras");
  89. expect(text).toContain("scoop install extras/opencode");
  90. unmount();
  91. });
  92. test("5 语言 messages/ 需包含 OpenCode 段落的关键翻译键", () => {
  93. for (const locale of locales) {
  94. const usageMessages = loadUsageMessages(locale);
  95. expect(usageMessages).toHaveProperty("opencode.title");
  96. expect(usageMessages).toHaveProperty("opencode.description");
  97. expect(usageMessages).toHaveProperty("opencode.installation.title");
  98. expect(usageMessages).toHaveProperty("opencode.installation.script.title");
  99. expect(usageMessages).toHaveProperty("opencode.installation.npm.title");
  100. expect(usageMessages).toHaveProperty("opencode.installation.npm.note");
  101. expect(usageMessages).toHaveProperty("opencode.installation.bun.title");
  102. expect(usageMessages).toHaveProperty("opencode.installation.macos.homebrew.title");
  103. expect(usageMessages).toHaveProperty("opencode.installation.linux.homebrew.title");
  104. expect(usageMessages).toHaveProperty("opencode.installation.linux.paru.title");
  105. expect(usageMessages).toHaveProperty("opencode.installation.windows.note");
  106. expect(usageMessages).toHaveProperty("opencode.configuration.title");
  107. expect(usageMessages).toHaveProperty("opencode.startup.title");
  108. expect(usageMessages).toHaveProperty("opencode.commonIssues.title");
  109. expect(usageMessages).toHaveProperty("layout.headerTitle");
  110. expect(usageMessages).toHaveProperty("layout.loginConsole");
  111. expect(usageMessages).toHaveProperty("placeholders.windowsUserName");
  112. expect(usageMessages).toHaveProperty("placeholders.shellConfig.linux");
  113. expect(usageMessages).toHaveProperty("placeholders.shellConfig.macos");
  114. expect(usageMessages).toHaveProperty("placeholders.codexVsCodeConfigFiles");
  115. expect(usageMessages).toHaveProperty("claudeCode.installation.nativeInstall.macos.curls");
  116. expect(usageMessages).toHaveProperty("snippets.comments.updateHomebrew");
  117. expect(usageMessages).toHaveProperty("snippets.comments.installNodeJs");
  118. expect(usageMessages).toHaveProperty("snippets.comments.ubuntuDebian");
  119. expect(usageMessages).toHaveProperty("snippets.comments.centosRhelFedora");
  120. expect(usageMessages).toHaveProperty("snippets.comments.addToPathIfMissing");
  121. expect(usageMessages).toHaveProperty("snippets.comments.checkEnvVar");
  122. expect(usageMessages).toHaveProperty("snippets.comments.testNetworkConnection");
  123. }
  124. });
  125. });