usage-doc-page.test.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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 UsageDocPage from "@/app/[locale]/usage-doc/page";
  12. vi.mock("@/i18n/routing", () => ({
  13. Link: ({
  14. href,
  15. children,
  16. ...rest
  17. }: {
  18. href: string;
  19. children: ReactNode;
  20. className?: string;
  21. }) => (
  22. <a href={href} {...rest}>
  23. {children}
  24. </a>
  25. ),
  26. }));
  27. function loadUsageMessages(locale: string) {
  28. return JSON.parse(
  29. fs.readFileSync(path.join(process.cwd(), "messages", locale, "usage.json"), "utf8")
  30. );
  31. }
  32. async function renderWithIntl(locale: string, node: ReactNode) {
  33. const container = document.createElement("div");
  34. document.body.appendChild(container);
  35. const root = createRoot(container);
  36. const usageMessages = loadUsageMessages(locale);
  37. await act(async () => {
  38. root.render(
  39. <NextIntlClientProvider locale={locale} messages={{ usage: usageMessages }} timeZone="UTC">
  40. {node}
  41. </NextIntlClientProvider>
  42. );
  43. });
  44. return {
  45. unmount: async () => {
  46. await act(async () => root.unmount());
  47. container.remove();
  48. },
  49. };
  50. }
  51. describe("UsageDocPage - 目录/快速链接交互", () => {
  52. test("应渲染 skip links,且登录态显示返回仪表盘链接", async () => {
  53. Object.defineProperty(window, "scrollTo", {
  54. value: vi.fn(),
  55. writable: true,
  56. });
  57. Object.defineProperty(document, "cookie", {
  58. configurable: true,
  59. get: () => "auth-token=test-token",
  60. });
  61. const { unmount } = await renderWithIntl("en", <UsageDocPage />);
  62. expect(document.querySelector('a[href="#main-content"]')).not.toBeNull();
  63. expect(document.querySelector('a[href="#toc-navigation"]')).not.toBeNull();
  64. const dashboardLink = document.querySelector('a[href="/dashboard"]');
  65. expect(dashboardLink).not.toBeNull();
  66. await unmount();
  67. Reflect.deleteProperty(document, "cookie");
  68. });
  69. test("ru 语言不应显示中文占位符与代码块注释", async () => {
  70. const { unmount } = await renderWithIntl("ru", <UsageDocPage />);
  71. const text = document.body.textContent || "";
  72. expect(text).not.toContain("你的用户名");
  73. expect(text).not.toContain("检查环境变量");
  74. expect(text).not.toContain("添加到 PATH");
  75. expect(text).toContain("C:\\Users\\your-username");
  76. await unmount();
  77. });
  78. test("目录项点击后应触发平滑滚动", async () => {
  79. const scrollToMock = vi.fn();
  80. Object.defineProperty(window, "scrollTo", {
  81. value: scrollToMock,
  82. writable: true,
  83. });
  84. const { unmount } = await renderWithIntl("en", <UsageDocPage />);
  85. const tocNav = document.querySelector("#toc-navigation nav");
  86. expect(tocNav).not.toBeNull();
  87. let tocButtons = tocNav?.querySelectorAll("button") ?? [];
  88. for (let i = 0; i < 10 && tocButtons.length === 0; i++) {
  89. await act(async () => {
  90. await new Promise((r) => setTimeout(r, 0));
  91. });
  92. tocButtons = tocNav?.querySelectorAll("button") ?? [];
  93. }
  94. expect(tocButtons.length).toBeGreaterThan(0);
  95. await act(async () => {
  96. (tocButtons[0] as HTMLButtonElement).click();
  97. });
  98. expect(scrollToMock).toHaveBeenCalled();
  99. await unmount();
  100. });
  101. });