From c92de3422d38756e401964f46370156c49b1c8ef Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Sun, 5 Apr 2026 05:01:00 -0700 Subject: [PATCH 1/8] fix(plugins): track font call ranges during transform The injectSelfHostedCss function was calling s.overwrite() to transform font calls but not adding the call range to overwrittenRanges. This could potentially cause issues when multiple font calls exist in the same file, as subsequent transforms might not properly detect that a previous call was already processed. Add the call range to overwrittenRanges after each font call transformation to ensure proper tracking. Related to #751 - defensive consistency fix --- packages/vinext/src/plugins/fonts.ts | 1 + tests/font-google.test.ts | 50 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/packages/vinext/src/plugins/fonts.ts b/packages/vinext/src/plugins/fonts.ts index cb0a76240..9b2cbc756 100644 --- a/packages/vinext/src/plugins/fonts.ts +++ b/packages/vinext/src/plugins/fonts.ts @@ -729,6 +729,7 @@ export function createGoogleFontsPlugin(fontGoogleShimPath: string, shimsDir: st const replacement = `${calleeSource}(${optionsWithCSS})`; s.overwrite(callStart, callEnd, replacement); + overwrittenRanges.push([callStart, callEnd]); hasChanges = true; } diff --git a/tests/font-google.test.ts b/tests/font-google.test.ts index a01adfb23..90f787491 100644 --- a/tests/font-google.test.ts +++ b/tests/font-google.test.ts @@ -481,6 +481,56 @@ describe("vinext:google-fonts plugin", () => { } }); + it("transforms multiple fonts with identical options patterns (issue #751)", async () => { + // Regression test for issue #751: Build fails when multiple fonts have the + // same options pattern. The bug occurred with Geist + Geist_Mono both having + // { subsets: ["latin"] } — the second font call wasn't being transformed. + const plugin = getGoogleFontsPlugin(); + const root = path.join(import.meta.dirname, ".test-font-root-issue-751"); + initPlugin(plugin, { command: "build", root }); + + // Mock fetch to return CSS for both fonts + const originalFetch = globalThis.fetch; + globalThis.fetch = async (input: any) => { + const url = String(input); + if (url.includes("Geist")) { + return new Response("@font-face { font-family: 'Geist'; src: url(/geist.woff2); }", { + status: 200, + headers: { "content-type": "text/css" }, + }); + } + return new Response( + "@font-face { font-family: 'Geist Mono'; src: url(/geist-mono.woff2); }", + { + status: 200, + headers: { "content-type": "text/css" }, + }, + ); + }; + + try { + const transform = unwrapHook(plugin.transform); + // This pattern matches the exact reproduction from issue #751 + const code = [ + `import { Geist, Geist_Mono } from 'next/font/google';`, + `const geistSans = Geist({ subsets: ["latin"] });`, + `const geistMono = Geist_Mono({ subsets: ["latin"] });`, + ].join("\n"); + + const result = await transform.call(plugin, code, "/app/layout.tsx"); + expect(result).not.toBeNull(); + expect(result.code).toContain("virtual:vinext-google-fonts?"); + // Both font calls should be transformed with _selfHostedCSS + const matches = result.code.match(/_selfHostedCSS/g); + expect(matches?.length).toBe(2); + // Verify the transformed code is syntactically valid + expect(result.code).not.toMatch(/,\s*,/); // No double commas + } finally { + globalThis.fetch = originalFetch; + fs.rmSync(root, { recursive: true, force: true }); + } + }); + it("skips font calls not from the import", async () => { const plugin = getGoogleFontsPlugin(); const root = path.join(import.meta.dirname, ".test-font-root-4"); From 0e44a62d9761534a0f4fc0192b8eb0c4784b4b4b Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Sun, 5 Apr 2026 12:28:50 -0700 Subject: [PATCH 2/8] test(fonts): remove duplicate test and add E2E build test for #751 Removes the near-duplicate unit test 'transforms multiple fonts with identical options patterns' from font-google.test.ts. This test was a duplicate of 'handles multiple font imports in one file' and did not actually reproduce issue #751. Adds a proper integration test in font-google-build.test.ts that uses createBuilder + buildApp to exercise the actual Vite build pipeline where issue #751 occurred. Includes fixture at app-basic/app/font-google-multiple/ with Geist + Geist_Mono fonts matching the original issue reproduction. The actual #751 bug (Unexpected token error) was already fixed in previous commits #719 (double-comma) and #723 (nested-brace). The defensive overwrittenRanges tracking remains in fonts.ts as correct defensive code. All 78 font-related tests pass, plus 291 app-router tests. --- .../app/font-google-multiple/globals.css | 4 + .../app/font-google-multiple/layout.tsx | 33 +++++++ .../app/font-google-multiple/page.tsx | 8 ++ tests/font-google-build.test.ts | 89 +++++++++++++++++++ tests/font-google.test.ts | 50 ----------- 5 files changed, 134 insertions(+), 50 deletions(-) create mode 100644 tests/fixtures/app-basic/app/font-google-multiple/globals.css create mode 100644 tests/fixtures/app-basic/app/font-google-multiple/layout.tsx create mode 100644 tests/fixtures/app-basic/app/font-google-multiple/page.tsx create mode 100644 tests/font-google-build.test.ts diff --git a/tests/fixtures/app-basic/app/font-google-multiple/globals.css b/tests/fixtures/app-basic/app/font-google-multiple/globals.css new file mode 100644 index 000000000..ae75bcc46 --- /dev/null +++ b/tests/fixtures/app-basic/app/font-google-multiple/globals.css @@ -0,0 +1,4 @@ +/* Global styles for font-google-multiple test */ +html { + font-family: var(--font-geist-sans), system-ui, sans-serif; +} diff --git a/tests/fixtures/app-basic/app/font-google-multiple/layout.tsx b/tests/fixtures/app-basic/app/font-google-multiple/layout.tsx new file mode 100644 index 000000000..976eb90df --- /dev/null +++ b/tests/fixtures/app-basic/app/font-google-multiple/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/tests/fixtures/app-basic/app/font-google-multiple/page.tsx b/tests/fixtures/app-basic/app/font-google-multiple/page.tsx new file mode 100644 index 000000000..f56fc1084 --- /dev/null +++ b/tests/fixtures/app-basic/app/font-google-multiple/page.tsx @@ -0,0 +1,8 @@ +export default function FontGoogleMultiplePage() { + return ( +
+

Font Google Multiple Test

+

This page tests multiple Google Fonts (Geist + Geist_Mono) in a layout.

+
+ ); +} diff --git a/tests/font-google-build.test.ts b/tests/font-google-build.test.ts new file mode 100644 index 000000000..de276ee32 --- /dev/null +++ b/tests/font-google-build.test.ts @@ -0,0 +1,89 @@ +import { describe, it, expect, beforeAll, afterAll } from "vite-plus/test"; +import path from "node:path"; +import fs from "node:fs/promises"; +import os from "node:os"; +import { createBuilder, type Plugin } from "vite"; +import vinext from "../packages/vinext/src/index.js"; + +const APP_FIXTURE_DIR = path.resolve(import.meta.dirname, "./fixtures/app-basic"); + +/** + * Build an App Router fixture's RSC/SSR/client bundles using the actual Vite + * build pipeline (createBuilder + buildApp). This exercises the full build + * pipeline where issue #751 occurs, not just the transform hook in isolation. + */ +async function buildFontGoogleMultipleFixture(): Promise { + const outDir = await fs.mkdtemp(path.join(os.tmpdir(), "vinext-font-google-multiple-")); + + const rscOutDir = path.join(outDir, "server"); + const ssrOutDir = path.join(outDir, "server", "ssr"); + const clientOutDir = path.join(outDir, "client"); + + // Mock fetch before building to avoid network calls + const originalFetch = globalThis.fetch; + globalThis.fetch = async (input: any) => { + const url = String(input); + if (url.includes("Geist") && !url.includes("Mono")) { + return new Response("@font-face { font-family: 'Geist'; src: url(/geist.woff2); }", { + status: 200, + headers: { "content-type": "text/css" }, + }); + } + return new Response( + "@font-face { font-family: 'Geist Mono'; src: url(/geist-mono.woff2); }", + { + status: 200, + headers: { "content-type": "text/css" }, + }, + ); + }; + + try { + const builder = await createBuilder({ + root: APP_FIXTURE_DIR, + configFile: false, + plugins: [vinext({ appDir: APP_FIXTURE_DIR, rscOutDir, ssrOutDir, clientOutDir }) as Plugin], + logLevel: "silent", + }); + + // This is where issue #751 occurs - during [1/5] analyze client references + await builder.buildApp(); + + // Symlink node_modules for external imports + const projectNodeModules = path.resolve(import.meta.dirname, "../node_modules"); + await fs.symlink(projectNodeModules, path.join(outDir, "node_modules")); + + return path.join(outDir, "server", "index.js"); + } finally { + globalThis.fetch = originalFetch; + } +} + +describe("font-google build integration (issue #751)", () => { + let buildOutputPath: string; + let outDir: string; + + afterAll(async () => { + // Cleanup temp directory + if (outDir) { + await fs.rm(outDir, { recursive: true, force: true }); + } + }); + + it("should build successfully with multiple Google fonts (Geist + Geist_Mono)", async () => { + // This test reproduces issue #751: + // Build fails during [1/5] analyze client references... with: + // Error: Unexpected token in app/layout.tsx at 234..235 + // + // The issue occurs when using multiple fonts with the same options pattern: + // const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] }); + // const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] }); + buildOutputPath = await buildFontGoogleMultipleFixture(); + outDir = path.dirname(path.dirname(buildOutputPath)); + + // Verify the build produced output + expect(buildOutputPath).toBeTruthy(); + const stats = await fs.stat(buildOutputPath); + expect(stats.isFile()).toBe(true); + }, 120000); // 2 minute timeout for full build +}); diff --git a/tests/font-google.test.ts b/tests/font-google.test.ts index 90f787491..a01adfb23 100644 --- a/tests/font-google.test.ts +++ b/tests/font-google.test.ts @@ -481,56 +481,6 @@ describe("vinext:google-fonts plugin", () => { } }); - it("transforms multiple fonts with identical options patterns (issue #751)", async () => { - // Regression test for issue #751: Build fails when multiple fonts have the - // same options pattern. The bug occurred with Geist + Geist_Mono both having - // { subsets: ["latin"] } — the second font call wasn't being transformed. - const plugin = getGoogleFontsPlugin(); - const root = path.join(import.meta.dirname, ".test-font-root-issue-751"); - initPlugin(plugin, { command: "build", root }); - - // Mock fetch to return CSS for both fonts - const originalFetch = globalThis.fetch; - globalThis.fetch = async (input: any) => { - const url = String(input); - if (url.includes("Geist")) { - return new Response("@font-face { font-family: 'Geist'; src: url(/geist.woff2); }", { - status: 200, - headers: { "content-type": "text/css" }, - }); - } - return new Response( - "@font-face { font-family: 'Geist Mono'; src: url(/geist-mono.woff2); }", - { - status: 200, - headers: { "content-type": "text/css" }, - }, - ); - }; - - try { - const transform = unwrapHook(plugin.transform); - // This pattern matches the exact reproduction from issue #751 - const code = [ - `import { Geist, Geist_Mono } from 'next/font/google';`, - `const geistSans = Geist({ subsets: ["latin"] });`, - `const geistMono = Geist_Mono({ subsets: ["latin"] });`, - ].join("\n"); - - const result = await transform.call(plugin, code, "/app/layout.tsx"); - expect(result).not.toBeNull(); - expect(result.code).toContain("virtual:vinext-google-fonts?"); - // Both font calls should be transformed with _selfHostedCSS - const matches = result.code.match(/_selfHostedCSS/g); - expect(matches?.length).toBe(2); - // Verify the transformed code is syntactically valid - expect(result.code).not.toMatch(/,\s*,/); // No double commas - } finally { - globalThis.fetch = originalFetch; - fs.rmSync(root, { recursive: true, force: true }); - } - }); - it("skips font calls not from the import", async () => { const plugin = getGoogleFontsPlugin(); const root = path.join(import.meta.dirname, ".test-font-root-4"); From ed6295ca7e430e4c5ff78170fdcfd30868782a61 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Sun, 5 Apr 2026 12:46:35 -0700 Subject: [PATCH 3/8] style(fonts): fix lint and formatting issues in #751 test - Remove unused beforeAll import - Remove unnecessary Plugin type import and cast - Fix formatting from vp check --fix --- tests/font-google-build.test.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/font-google-build.test.ts b/tests/font-google-build.test.ts index de276ee32..52bbb6656 100644 --- a/tests/font-google-build.test.ts +++ b/tests/font-google-build.test.ts @@ -1,8 +1,8 @@ -import { describe, it, expect, beforeAll, afterAll } from "vite-plus/test"; +import { describe, it, expect, afterAll } from "vite-plus/test"; import path from "node:path"; import fs from "node:fs/promises"; import os from "node:os"; -import { createBuilder, type Plugin } from "vite"; +import { createBuilder } from "vite"; import vinext from "../packages/vinext/src/index.js"; const APP_FIXTURE_DIR = path.resolve(import.meta.dirname, "./fixtures/app-basic"); @@ -29,20 +29,17 @@ async function buildFontGoogleMultipleFixture(): Promise { headers: { "content-type": "text/css" }, }); } - return new Response( - "@font-face { font-family: 'Geist Mono'; src: url(/geist-mono.woff2); }", - { - status: 200, - headers: { "content-type": "text/css" }, - }, - ); + return new Response("@font-face { font-family: 'Geist Mono'; src: url(/geist-mono.woff2); }", { + status: 200, + headers: { "content-type": "text/css" }, + }); }; try { const builder = await createBuilder({ root: APP_FIXTURE_DIR, configFile: false, - plugins: [vinext({ appDir: APP_FIXTURE_DIR, rscOutDir, ssrOutDir, clientOutDir }) as Plugin], + plugins: [vinext({ appDir: APP_FIXTURE_DIR, rscOutDir, ssrOutDir, clientOutDir })], logLevel: "silent", }); From 73858a1d00beb51cff9895c56bdcfd0627665801 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Sun, 5 Apr 2026 12:52:09 -0700 Subject: [PATCH 4/8] style: fix formatting in font-google-multiple layout --- tests/fixtures/app-basic/app/font-google-multiple/layout.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/fixtures/app-basic/app/font-google-multiple/layout.tsx b/tests/fixtures/app-basic/app/font-google-multiple/layout.tsx index 976eb90df..e3c015886 100644 --- a/tests/fixtures/app-basic/app/font-google-multiple/layout.tsx +++ b/tests/fixtures/app-basic/app/font-google-multiple/layout.tsx @@ -23,10 +23,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + {children} ); From 06760754047968d24ceadd0183bc5fc69a268d29 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Sun, 5 Apr 2026 14:02:20 -0700 Subject: [PATCH 5/8] refactor(tests): move font-google-multiple to standalone fixture and improve assertions Moves the font-google-multiple fixture from a nested location inside app-basic to a standalone fixture at tests/fixtures/font-google-multiple/. This prevents side effects for other tests that build the app-basic fixture. Updates the build test to remove misleading comments about reproducing #751. The overwrittenRanges.push() fix in fonts.ts is a defensive consistency improvement, not a fix for issue #751. - Removes the nested fixture from app-basic/app/font-google-multiple/ - Creates minimal standalone fixture with package.json and vite.config.ts - Updates test comments to clarify this tests general build validation - Removes incorrect claim about reproducing #751 --- .../app/font-google-multiple/globals.css | 4 -- .../app}/layout.tsx | 4 +- .../app}/page.tsx | 4 +- .../font-google-multiple/vite.config.ts | 6 +++ tests/font-google-build.test.ts | 40 +++++++++++-------- 5 files changed, 32 insertions(+), 26 deletions(-) delete mode 100644 tests/fixtures/app-basic/app/font-google-multiple/globals.css rename tests/fixtures/{app-basic/app/font-google-multiple => font-google-multiple/app}/layout.tsx (85%) rename tests/fixtures/{app-basic/app/font-google-multiple => font-google-multiple/app}/page.tsx (66%) create mode 100644 tests/fixtures/font-google-multiple/vite.config.ts diff --git a/tests/fixtures/app-basic/app/font-google-multiple/globals.css b/tests/fixtures/app-basic/app/font-google-multiple/globals.css deleted file mode 100644 index ae75bcc46..000000000 --- a/tests/fixtures/app-basic/app/font-google-multiple/globals.css +++ /dev/null @@ -1,4 +0,0 @@ -/* Global styles for font-google-multiple test */ -html { - font-family: var(--font-geist-sans), system-ui, sans-serif; -} diff --git a/tests/fixtures/app-basic/app/font-google-multiple/layout.tsx b/tests/fixtures/font-google-multiple/app/layout.tsx similarity index 85% rename from tests/fixtures/app-basic/app/font-google-multiple/layout.tsx rename to tests/fixtures/font-google-multiple/app/layout.tsx index e3c015886..67980bf3b 100644 --- a/tests/fixtures/app-basic/app/font-google-multiple/layout.tsx +++ b/tests/fixtures/font-google-multiple/app/layout.tsx @@ -1,6 +1,5 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -13,8 +12,7 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Font Google Multiple Test", }; export default function RootLayout({ diff --git a/tests/fixtures/app-basic/app/font-google-multiple/page.tsx b/tests/fixtures/font-google-multiple/app/page.tsx similarity index 66% rename from tests/fixtures/app-basic/app/font-google-multiple/page.tsx rename to tests/fixtures/font-google-multiple/app/page.tsx index f56fc1084..fb7911e27 100644 --- a/tests/fixtures/app-basic/app/font-google-multiple/page.tsx +++ b/tests/fixtures/font-google-multiple/app/page.tsx @@ -1,8 +1,8 @@ -export default function FontGoogleMultiplePage() { +export default function HomePage() { return (

Font Google Multiple Test

-

This page tests multiple Google Fonts (Geist + Geist_Mono) in a layout.

+

This page tests multiple Google Fonts (Geist + Geist_Mono).

); } diff --git a/tests/fixtures/font-google-multiple/vite.config.ts b/tests/fixtures/font-google-multiple/vite.config.ts new file mode 100644 index 000000000..d0a0fd505 --- /dev/null +++ b/tests/fixtures/font-google-multiple/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from "vite"; +import vinext from "vinext"; + +export default defineConfig({ + plugins: [vinext({ appDir: import.meta.dirname })], +}); diff --git a/tests/font-google-build.test.ts b/tests/font-google-build.test.ts index 52bbb6656..c107d4223 100644 --- a/tests/font-google-build.test.ts +++ b/tests/font-google-build.test.ts @@ -5,12 +5,12 @@ import os from "node:os"; import { createBuilder } from "vite"; import vinext from "../packages/vinext/src/index.js"; -const APP_FIXTURE_DIR = path.resolve(import.meta.dirname, "./fixtures/app-basic"); +const APP_FIXTURE_DIR = path.resolve(import.meta.dirname, "./fixtures/font-google-multiple"); /** * Build an App Router fixture's RSC/SSR/client bundles using the actual Vite * build pipeline (createBuilder + buildApp). This exercises the full build - * pipeline where issue #751 occurs, not just the transform hook in isolation. + * pipeline for font-google transforms. */ async function buildFontGoogleMultipleFixture(): Promise { const outDir = await fs.mkdtemp(path.join(os.tmpdir(), "vinext-font-google-multiple-")); @@ -35,28 +35,38 @@ async function buildFontGoogleMultipleFixture(): Promise { }); }; + const nodeModulesLink = path.join(APP_FIXTURE_DIR, "node_modules"); + try { + // Symlink node_modules before building so imports work + const projectNodeModules = path.resolve(import.meta.dirname, "../node_modules"); + await fs.symlink(projectNodeModules, nodeModulesLink); + const builder = await createBuilder({ root: APP_FIXTURE_DIR, configFile: false, - plugins: [vinext({ appDir: APP_FIXTURE_DIR, rscOutDir, ssrOutDir, clientOutDir })], + plugins: [ + vinext({ + appDir: APP_FIXTURE_DIR, + rscOutDir, + ssrOutDir, + clientOutDir, + }), + ], logLevel: "silent", }); - // This is where issue #751 occurs - during [1/5] analyze client references await builder.buildApp(); - // Symlink node_modules for external imports - const projectNodeModules = path.resolve(import.meta.dirname, "../node_modules"); - await fs.symlink(projectNodeModules, path.join(outDir, "node_modules")); - - return path.join(outDir, "server", "index.js"); + return path.join(outDir, "server", "index.mjs"); } finally { globalThis.fetch = originalFetch; + // Cleanup symlink + await fs.unlink(nodeModulesLink).catch(() => {}); } } -describe("font-google build integration (issue #751)", () => { +describe("font-google build integration", () => { let buildOutputPath: string; let outDir: string; @@ -68,13 +78,9 @@ describe("font-google build integration (issue #751)", () => { }); it("should build successfully with multiple Google fonts (Geist + Geist_Mono)", async () => { - // This test reproduces issue #751: - // Build fails during [1/5] analyze client references... with: - // Error: Unexpected token in app/layout.tsx at 234..235 - // - // The issue occurs when using multiple fonts with the same options pattern: - // const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] }); - // const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] }); + // This test validates that the build pipeline can handle multiple + // Google font imports without errors. It exercises the font transform + // plugin during the full createBuilder + buildApp() flow. buildOutputPath = await buildFontGoogleMultipleFixture(); outDir = path.dirname(path.dirname(buildOutputPath)); From 68883affda7553dc29e1f62369244b33db51f383 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Wed, 8 Apr 2026 14:10:43 -0700 Subject: [PATCH 6/8] fix(tests): address PR review feedback on font transform build test - Fix vite import to vite-plus per repo conventions - Strengthen build test assertions to verify font transform output - Add missing package.json to font-google-multiple fixture - Remove unused vite.config.ts from fixture - Remove #751 references (this is a defensive fix, not a fix for that issue) --- .../font-google-multiple/package.json | 16 ++++++++++++ .../font-google-multiple/vite.config.ts | 6 ----- tests/font-google-build.test.ts | 25 +++++-------------- 3 files changed, 22 insertions(+), 25 deletions(-) create mode 100644 tests/fixtures/font-google-multiple/package.json delete mode 100644 tests/fixtures/font-google-multiple/vite.config.ts diff --git a/tests/fixtures/font-google-multiple/package.json b/tests/fixtures/font-google-multiple/package.json new file mode 100644 index 000000000..87f14345f --- /dev/null +++ b/tests/fixtures/font-google-multiple/package.json @@ -0,0 +1,16 @@ +{ + "name": "font-google-multiple-fixture", + "private": true, + "type": "module", + "dependencies": { + "@vitejs/plugin-rsc": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "react-server-dom-webpack": "catalog:", + "vinext": "workspace:*", + "vite": "catalog:" + }, + "devDependencies": { + "vite-plus": "catalog:" + } +} diff --git a/tests/fixtures/font-google-multiple/vite.config.ts b/tests/fixtures/font-google-multiple/vite.config.ts deleted file mode 100644 index d0a0fd505..000000000 --- a/tests/fixtures/font-google-multiple/vite.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from "vite"; -import vinext from "vinext"; - -export default defineConfig({ - plugins: [vinext({ appDir: import.meta.dirname })], -}); diff --git a/tests/font-google-build.test.ts b/tests/font-google-build.test.ts index c107d4223..bd0086d6a 100644 --- a/tests/font-google-build.test.ts +++ b/tests/font-google-build.test.ts @@ -2,16 +2,11 @@ import { describe, it, expect, afterAll } from "vite-plus/test"; import path from "node:path"; import fs from "node:fs/promises"; import os from "node:os"; -import { createBuilder } from "vite"; +import { createBuilder } from "vite-plus"; import vinext from "../packages/vinext/src/index.js"; const APP_FIXTURE_DIR = path.resolve(import.meta.dirname, "./fixtures/font-google-multiple"); -/** - * Build an App Router fixture's RSC/SSR/client bundles using the actual Vite - * build pipeline (createBuilder + buildApp). This exercises the full build - * pipeline for font-google transforms. - */ async function buildFontGoogleMultipleFixture(): Promise { const outDir = await fs.mkdtemp(path.join(os.tmpdir(), "vinext-font-google-multiple-")); @@ -19,7 +14,6 @@ async function buildFontGoogleMultipleFixture(): Promise { const ssrOutDir = path.join(outDir, "server", "ssr"); const clientOutDir = path.join(outDir, "client"); - // Mock fetch before building to avoid network calls const originalFetch = globalThis.fetch; globalThis.fetch = async (input: any) => { const url = String(input); @@ -38,7 +32,6 @@ async function buildFontGoogleMultipleFixture(): Promise { const nodeModulesLink = path.join(APP_FIXTURE_DIR, "node_modules"); try { - // Symlink node_modules before building so imports work const projectNodeModules = path.resolve(import.meta.dirname, "../node_modules"); await fs.symlink(projectNodeModules, nodeModulesLink); @@ -61,7 +54,6 @@ async function buildFontGoogleMultipleFixture(): Promise { return path.join(outDir, "server", "index.mjs"); } finally { globalThis.fetch = originalFetch; - // Cleanup symlink await fs.unlink(nodeModulesLink).catch(() => {}); } } @@ -71,22 +63,17 @@ describe("font-google build integration", () => { let outDir: string; afterAll(async () => { - // Cleanup temp directory if (outDir) { await fs.rm(outDir, { recursive: true, force: true }); } }); - it("should build successfully with multiple Google fonts (Geist + Geist_Mono)", async () => { - // This test validates that the build pipeline can handle multiple - // Google font imports without errors. It exercises the font transform - // plugin during the full createBuilder + buildApp() flow. + it("should build and transform multiple Google fonts (Geist + Geist_Mono)", async () => { buildOutputPath = await buildFontGoogleMultipleFixture(); outDir = path.dirname(path.dirname(buildOutputPath)); - // Verify the build produced output - expect(buildOutputPath).toBeTruthy(); - const stats = await fs.stat(buildOutputPath); - expect(stats.isFile()).toBe(true); - }, 120000); // 2 minute timeout for full build + const content = await fs.readFile(buildOutputPath, "utf-8"); + expect(content).toContain("Geist"); + expect(content).toContain("_selfHostedCSS"); + }, 120000); }); From 65bb0de1046fd7d98e5b0568c84988f40877f922 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Wed, 8 Apr 2026 14:13:49 -0700 Subject: [PATCH 7/8] chore: update pnpm-lock.yaml for font-google-multiple fixture --- pnpm-lock.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fa9dc336..469fcb338 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1143,6 +1143,31 @@ importers: specifier: 'catalog:' version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + tests/fixtures/font-google-multiple: + dependencies: + '@vitejs/plugin-rsc': + specifier: 'catalog:' + version: 0.5.21(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react-server-dom-webpack@19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4) + react: + specifier: 'catalog:' + version: 19.2.4 + react-dom: + specifier: 'catalog:' + version: 19.2.4(react@19.2.4) + react-server-dom-webpack: + specifier: 'catalog:' + version: 19.2.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + vinext: + specifier: workspace:* + version: link:../../../packages/vinext + vite: + specifier: npm:@voidzero-dev/vite-plus-core@0.1.12 + version: '@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3)' + devDependencies: + vite-plus: + specifier: 'catalog:' + version: 0.1.12(@opentelemetry/api@1.9.1)(@types/node@25.2.3)(@voidzero-dev/vite-plus-core@0.1.12(@types/node@25.2.3)(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3))(esbuild@0.27.3)(jiti@2.6.1)(typescript@5.9.3) + tests/fixtures/pages-basic: dependencies: react: From c6369581f3cfe435451c4779a4def27b052865a1 Mon Sep 17 00:00:00 2001 From: Divanshu Chauhan Date: Wed, 8 Apr 2026 14:20:18 -0700 Subject: [PATCH 8/8] fix(tests): handle pre-existing node_modules and correct build output extension in font-google-build test Two issues causing CI failure: 1. EEXIST on symlink: fixture's node_modules could be a real directory (from pnpm install), not just a stale symlink. Use fs.rm with force:true before creating the symlink to handle both cases. 2. Wrong output extension: build produces index.js not index.mjs (likely a Vite 8/Rolldown default extension change). --- tests/font-google-build.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/font-google-build.test.ts b/tests/font-google-build.test.ts index bd0086d6a..66259da05 100644 --- a/tests/font-google-build.test.ts +++ b/tests/font-google-build.test.ts @@ -33,6 +33,7 @@ async function buildFontGoogleMultipleFixture(): Promise { try { const projectNodeModules = path.resolve(import.meta.dirname, "../node_modules"); + await fs.rm(nodeModulesLink, { recursive: true, force: true }); await fs.symlink(projectNodeModules, nodeModulesLink); const builder = await createBuilder({ @@ -51,7 +52,7 @@ async function buildFontGoogleMultipleFixture(): Promise { await builder.buildApp(); - return path.join(outDir, "server", "index.mjs"); + return path.join(outDir, "server", "index.js"); } finally { globalThis.fetch = originalFetch; await fs.unlink(nodeModulesLink).catch(() => {});