usage-doc-page.test.tsx 3.4 KB

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