Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions packages/react-grab/tests/get-next-base-path.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";

const globalWithDocument = globalThis as { document?: unknown };
let originalDocument: unknown;

const stubScriptSource = (source: string | null): void => {
globalWithDocument.document = {
querySelector: () => (source === null ? null : { src: source }),
};
};

// getNextBasePath memoizes at module scope with no reset hook, so each branch
// needs a freshly imported module to act as its "first call".
const loadGetNextBasePath = async (): Promise<() => string> => {
vi.resetModules();
return (await import("../src/utils/get-next-base-path.js")).getNextBasePath;
};

beforeEach(() => {
originalDocument = globalWithDocument.document;
});

afterEach(() => {
globalWithDocument.document = originalDocument;
vi.resetModules();
});

describe("getNextBasePath", () => {
it("returns the prefix before /_next/ when a basePath is configured", async () => {
stubScriptSource("http://localhost:3000/app/_next/static/chunks/main.js");
const getNextBasePath = await loadGetNextBasePath();
expect(getNextBasePath()).toBe("/app");
});

it("returns an empty string when scripts load from the root /_next/", async () => {
stubScriptSource("http://localhost:3000/_next/static/chunks/main.js");
const getNextBasePath = await loadGetNextBasePath();
expect(getNextBasePath()).toBe("");
});

it("returns an empty string when no /_next/ script is present", async () => {
stubScriptSource(null);
const getNextBasePath = await loadGetNextBasePath();
expect(getNextBasePath()).toBe("");
});

it("memoizes the first computed base path", async () => {
stubScriptSource("http://localhost:3000/app/_next/static/chunks/main.js");
const getNextBasePath = await loadGetNextBasePath();
expect(getNextBasePath()).toBe("/app");

stubScriptSource("http://localhost:3000/other/_next/static/chunks/main.js");
expect(getNextBasePath()).toBe("/app");
});
});
65 changes: 65 additions & 0 deletions packages/react-grab/tests/is-next-project-runtime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test";
import { isNextProjectRuntime } from "../src/utils/is-next-project-runtime.js";

interface DocumentStub {
getElementById: (id: string) => unknown;
querySelector: (selector: string) => unknown;
}

const globalWithDocument = globalThis as { document?: unknown };
let originalDocument: unknown;

const stubDocument = (stub: DocumentStub | undefined): void => {
globalWithDocument.document = stub;
};

beforeEach(() => {
originalDocument = globalWithDocument.document;
});

afterEach(() => {
globalWithDocument.document = originalDocument;
});

describe("isNextProjectRuntime", () => {
it("is false when there is no document (e.g. server runtime)", () => {
stubDocument(undefined);
expect(isNextProjectRuntime(true)).toBe(false);
});

it("detects the __NEXT_DATA__ script (Pages Router)", () => {
stubDocument({
getElementById: (id) => (id === "__NEXT_DATA__" ? {} : null),
querySelector: () => null,
});
expect(isNextProjectRuntime(true)).toBe(true);
});

it("detects the nextjs-portal element (App Router dev overlay)", () => {
stubDocument({
getElementById: () => null,
querySelector: (selector) => (selector === "nextjs-portal" ? {} : null),
});
expect(isNextProjectRuntime(true)).toBe(true);
});

it("is false when neither Next marker is present", () => {
stubDocument({ getElementById: () => null, querySelector: () => null });
expect(isNextProjectRuntime(true)).toBe(false);
});

it("memoizes the result until revalidation is requested", () => {
stubDocument({
getElementById: (id) => (id === "__NEXT_DATA__" ? {} : null),
querySelector: () => null,
});
expect(isNextProjectRuntime(true)).toBe(true);

// The cached value holds even though the markers are now gone...
stubDocument({ getElementById: () => null, querySelector: () => null });
expect(isNextProjectRuntime()).toBe(true);

// ...until a revalidate pass re-reads the DOM.
expect(isNextProjectRuntime(true)).toBe(false);
});
});
Loading