|
| 1 | +/** |
| 2 | + * @vitest-environment jsdom |
| 3 | + */ |
| 4 | + |
| 5 | +import * as React from "react"; |
| 6 | +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; |
| 7 | + |
| 8 | +// --- Mock react-dom/client for ESM --- |
| 9 | +const renderSpy = vi.fn(); |
| 10 | +const createRootMock = vi.fn(() => ({ render: renderSpy })); |
| 11 | +vi.mock("react-dom/client", () => ({ createRoot: createRootMock })); |
| 12 | + |
| 13 | +// Mock InstUISettingsProvider and App |
| 14 | +vi.mock("@instructure/ui", () => ({ |
| 15 | + InstUISettingsProvider: ({ children }: { children: React.ReactNode }) => ( |
| 16 | + <div data-testid="instui-provider">{children}</div> |
| 17 | + ), |
| 18 | +})); |
| 19 | +vi.mock("./App", () => ({ |
| 20 | + default: function App() { |
| 21 | + return <div data-testid="app" />; |
| 22 | + }, |
| 23 | +})); |
| 24 | + |
| 25 | +declare global { |
| 26 | + // eslint-disable-next-line no-var |
| 27 | + var __vite_ssr_module_cache__: Record<string, unknown> | undefined; |
| 28 | +} |
| 29 | + |
| 30 | +describe("main.tsx", () => { |
| 31 | + let rootElem: HTMLElement | null; |
| 32 | + |
| 33 | + beforeEach(() => { |
| 34 | + // Remove cached module to force re-execution for every test |
| 35 | + const viteCache = globalThis.__vite_ssr_module_cache__; |
| 36 | + if (typeof viteCache === "object" && viteCache !== null) { |
| 37 | + Object.keys(viteCache).forEach((key) => { |
| 38 | + if (key.includes("/main.tsx")) { |
| 39 | + delete viteCache[key]; |
| 40 | + } |
| 41 | + }); |
| 42 | + } |
| 43 | + rootElem = document.createElement("div"); |
| 44 | + rootElem.id = "root"; |
| 45 | + document.body.appendChild(rootElem); |
| 46 | + renderSpy.mockClear(); |
| 47 | + createRootMock.mockClear(); |
| 48 | + }); |
| 49 | + |
| 50 | + afterEach(() => { |
| 51 | + const elem = document.getElementById("root"); |
| 52 | + if (elem) elem.remove(); |
| 53 | + }); |
| 54 | + |
| 55 | + it("throws an error if root element is not found", async () => { |
| 56 | + if (rootElem?.parentNode) { |
| 57 | + rootElem.parentNode.removeChild(rootElem); |
| 58 | + } |
| 59 | + // Remove cached module to force re-execution |
| 60 | + const viteCache = globalThis.__vite_ssr_module_cache__; |
| 61 | + if (typeof viteCache === "object" && viteCache !== null) { |
| 62 | + Object.keys(viteCache).forEach((key) => { |
| 63 | + if (key.includes("/main.tsx")) { |
| 64 | + delete viteCache[key]; |
| 65 | + } |
| 66 | + }); |
| 67 | + } |
| 68 | + const { mountApp } = await import("./main"); |
| 69 | + expect(() => mountApp()).toThrow("Root element not found"); |
| 70 | + }); |
| 71 | + |
| 72 | + it("calls createRoot with the root element", async () => { |
| 73 | + const { mountApp } = await import("./main"); |
| 74 | + mountApp(); |
| 75 | + expect(createRootMock).toHaveBeenCalledWith(rootElem); |
| 76 | + }); |
| 77 | + |
| 78 | + it("renders StrictMode > InstUISettingsProvider > App", async () => { |
| 79 | + const { mountApp } = await import("./main"); |
| 80 | + mountApp(); |
| 81 | + expect(renderSpy).toHaveBeenCalledTimes(1); |
| 82 | + |
| 83 | + // Check the rendered tree structure |
| 84 | + const rendered = renderSpy.mock.calls[0][0]; |
| 85 | + // Should be <StrictMode> |
| 86 | + expect(React.isValidElement(rendered)).toBe(true); |
| 87 | + expect(rendered.type).toBe(React.StrictMode); |
| 88 | + |
| 89 | + // Should contain InstUISettingsProvider |
| 90 | + const provider = React.Children.only(rendered.props.children); |
| 91 | + expect( |
| 92 | + provider.type.name || |
| 93 | + provider.type.displayName || |
| 94 | + (typeof provider.type === "string" ? provider.type : ""), |
| 95 | + ).toMatch(/InstUISettingsProvider/); |
| 96 | + |
| 97 | + // Should contain App |
| 98 | + const app = React.Children.only(provider.props.children); |
| 99 | + expect( |
| 100 | + app.type.name || |
| 101 | + app.type.displayName || |
| 102 | + (typeof app.type === "string" ? app.type : ""), |
| 103 | + ).toMatch(/App/); |
| 104 | + }); |
| 105 | +}); |
0 commit comments