diff --git a/package-lock.json b/package-lock.json index 1ddcb673..715451d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10947,7 +10947,7 @@ "semver": "^7.5.3", "tmp-promise": "^3.0.3", "tsup": "^8.0.0", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": "^14.16.0 || >=16.0.0" @@ -11047,7 +11047,7 @@ "@netlify/types": "2.0.2", "npm-run-all2": "^7.0.2", "tsup": "^8.0.0", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": ">=20.6.1" @@ -11074,7 +11074,7 @@ "@netlify/api": "^14.0.3", "@netlify/types": "2.0.2", "tsup": "^8.0.0", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": ">=20.6.1" @@ -11107,7 +11107,7 @@ "@types/write-file-atomic": "^4.0.3", "tmp-promise": "^3.0.3", "tsup": "^8.0.0", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": "^18.14.0 || >=20" @@ -11232,7 +11232,7 @@ "devDependencies": { "@netlify/types": "2.0.2", "tsup": "^8.0.0", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": ">=18.0.0" @@ -11263,7 +11263,7 @@ "semver": "^7.6.3", "tsd": "^0.32.0", "tsup": "^8.0.2", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": ">=18.0.0" @@ -11441,7 +11441,7 @@ "@netlify/dev-utils": "^3.2.0", "@types/node": "^20.17.57", "tsup": "^8.0.0", - "vitest": "^3.2.2" + "vitest": "^3.1.4" }, "engines": { "node": ">=20.6.1" @@ -11470,7 +11470,7 @@ "devDependencies": { "@netlify/dev-utils": "^3.2.0", "tsup": "^8.5.0", - "vitest": "^3.2.2" + "vitest": "^3.1.4" }, "engines": { "node": ">=20.6.1" @@ -11490,7 +11490,7 @@ "devDependencies": { "npm-run-all2": "^7.0.2", "tsup": "^8.0.0", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": "^18.14.0 || >=20.6.1" @@ -11593,7 +11593,7 @@ "@netlify/dev-utils": "3.2.0", "@types/jsonwebtoken": "9.0.9", "tsup": "^8.5.0", - "vitest": "^3.2.2" + "vitest": "^3.1.4" }, "engines": { "node": ">=20.6.1" @@ -11613,7 +11613,7 @@ "@netlify/dev-utils": "^3.2.0", "@types/node": "^20.17.57", "tsup": "^8.0.0", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": ">=20.6.1" @@ -11626,7 +11626,7 @@ "devDependencies": { "@types/node": "^18.19.110", "tsup": "^8.0.0", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": "^18.14.0 || >=20" @@ -11669,7 +11669,7 @@ "@netlify/dev-utils": "^3.2.0", "@types/mime-types": "^2.1.4", "tsup": "^8.5.0", - "vitest": "^3.2.2" + "vitest": "^3.1.4" }, "engines": { "node": ">=20.6.1" @@ -11716,13 +11716,13 @@ "playwright": "^1.52.0", "tsup": "^8.0.0", "vite": "^6.3.4", - "vitest": "^3.2.2" + "vitest": "^3.0.0" }, "engines": { "node": "^20.6.1 || >=22" }, "peerDependencies": { - "vite": "^6" + "vite": "^5 || ^6" } }, "packages/vite-plugin/node_modules/@types/node": { diff --git a/packages/vite-plugin/package.json b/packages/vite-plugin/package.json index cbb129c2..189b73d6 100644 --- a/packages/vite-plugin/package.json +++ b/packages/vite-plugin/package.json @@ -41,6 +41,6 @@ "chalk": "^5.4.1" }, "peerDependencies": { - "vite": "^6" + "vite": "^5 || ^6" } } diff --git a/packages/vite-plugin/src/main.test.ts b/packages/vite-plugin/src/main.test.ts index 52afe1f4..a59a7f53 100644 --- a/packages/vite-plugin/src/main.test.ts +++ b/packages/vite-plugin/src/main.test.ts @@ -55,25 +55,26 @@ const createMockViteLogger = () => { } } -describe('Plugin constructor', () => { - test('Is a no-op when running in the Netlify CLI', () => { - process.env.NETLIFY_DEV = 'true' - - expect(netlify()).toEqual([]) - }) +const originalEnv = { ...process.env } +beforeEach(() => { + process.env = { ...originalEnv } }) -describe('configureServer', { timeout: 15_000 }, () => { - const originalEnv = { ...process.env } - beforeEach(() => { - process.env = { ...originalEnv } +describe.for([['5.0.0'], ['6.0.0']])('Vite %s', ([viteVersion]) => { + describe('Plugin constructor', () => { + test('Is a no-op when running in the Netlify CLI', () => { + process.env.NETLIFY_DEV = 'true' + + expect(netlify()).toEqual([]) + }) }) - test('Populates Netlify runtime environment (globals and env vars)', async () => { - const fixture = new Fixture() - .withFile( - 'vite.config.js', - `import { defineConfig } from 'vite'; + describe('configureServer', { timeout: 15_000 }, () => { + test('Populates Netlify runtime environment (globals and env vars)', async () => { + const fixture = new Fixture() + .withFile( + 'vite.config.js', + `import { defineConfig } from 'vite'; import netlify from '@netlify/vite-plugin'; export default defineConfig({ @@ -81,101 +82,36 @@ describe('configureServer', { timeout: 15_000 }, () => { netlify({ middleware: false }) ] });`, - ) - .withFile( - 'index.html', - ` + ) + .withFile( + 'index.html', + ` Hello World

Hello from the browser

`, - ) - const directory = await fixture.create() - await fixture - .withPackages({ - vite: '6.0.0', - '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), - }) - .create() - - const { server } = await startTestServer({ - root: directory, - }) - - expect((globalThis as Record).Netlify).toBeInstanceOf(Object) - expect(process.env).toHaveProperty('NETLIFY_LOCAL', 'true') - expect(process.env).toHaveProperty('CONTEXT', 'dev') - - await server.close() - await fixture.destroy() - }) + ) + const directory = await fixture.create() + await fixture + .withPackages({ + vite: viteVersion, + '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + }) + .create() - test('Prints a basic message on server start', async () => { - const fixture = new Fixture() - .withFile( - 'vite.config.js', - `import { defineConfig } from 'vite'; - import netlify from '@netlify/vite-plugin'; - - export default defineConfig({ - plugins: [ - netlify({ middleware: false }) - ] - });`, - ) - .withFile( - 'index.html', - ` - - Hello World -

Hello from the browser

- `, - ) - const directory = await fixture.create() - await fixture - .withPackages({ - vite: '6.0.0', - '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + const { server } = await startTestServer({ + root: directory, }) - .create() - - const mockLogger = createMockViteLogger() - const { server } = await startTestServer({ - root: directory, - logLevel: 'info', - customLogger: mockLogger, - }) - - expect(mockLogger.error).not.toHaveBeenCalled() - expect(mockLogger.warn).not.toHaveBeenCalled() - expect(mockLogger.warnOnce).not.toHaveBeenCalled() - expect(mockLogger.info).toHaveBeenCalledTimes(2) - expect(mockLogger.info).toHaveBeenNthCalledWith(1, 'Environment loaded', expect.objectContaining({})) - expect(mockLogger.info).toHaveBeenNthCalledWith( - 2, - '💭 Linking this project to a Netlify site lets you deploy your site, use any environment variables \ -defined on your team and site and much more. Run npx netlify init to get started.', - expect.objectContaining({}), - ) - - await server.close() - await fixture.destroy() - }) - - describe('Middleware enabled', () => { - let browser: Browser - let page: Page - beforeEach(async () => { - browser = await chromium.launch() - page = await browser.newPage() - }) + expect((globalThis as Record).Netlify).toBeInstanceOf(Object) + expect(process.env).toHaveProperty('NETLIFY_LOCAL', 'true') + expect(process.env).toHaveProperty('CONTEXT', 'dev') - afterEach(async () => { - await browser.close() + await server.close() + await fixture.destroy() }) - test('Prints a message listing emulated features on server start', async () => { + test('Prints a basic message on server start', async () => { const fixture = new Fixture() .withFile( 'vite.config.js', @@ -184,10 +120,7 @@ defined on your team and site and much more. Run npx netlify init to get started export default defineConfig({ plugins: [ - netlify({ - middleware: true, - edgeFunctions: { enabled: false }, - }) + netlify({ middleware: false }) ] });`, ) @@ -202,7 +135,7 @@ defined on your team and site and much more. Run npx netlify init to get started const directory = await fixture.create() await fixture .withPackages({ - vite: '6.0.0', + vite: viteVersion, '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), }) .create() @@ -217,16 +150,12 @@ defined on your team and site and much more. Run npx netlify init to get started expect(mockLogger.error).not.toHaveBeenCalled() expect(mockLogger.warn).not.toHaveBeenCalled() expect(mockLogger.warnOnce).not.toHaveBeenCalled() - expect(mockLogger.info).toHaveBeenCalledTimes(3) + expect(mockLogger.info).toHaveBeenCalledTimes(2) expect(mockLogger.info).toHaveBeenNthCalledWith(1, 'Environment loaded', expect.objectContaining({})) expect(mockLogger.info).toHaveBeenNthCalledWith( 2, - 'Middleware loaded. Emulating features: blobs, environmentVariables, functions, headers, images, redirects, static.', - expect.objectContaining({}), - ) - expect(mockLogger.info).toHaveBeenNthCalledWith( - 3, - expect.stringContaining('Linking this project to a Netlify site'), + '💭 Linking this project to a Netlify site lets you deploy your site, use any environment variables \ +defined on your team and site and much more. Run npx netlify init to get started.', expect.objectContaining({}), ) @@ -234,75 +163,147 @@ defined on your team and site and much more. Run npx netlify init to get started await fixture.destroy() }) - test('Returns static files from project dir', async () => { - const fixture = new Fixture() - .withFile( - 'vite.config.js', - `import { defineConfig } from 'vite'; + describe('Middleware enabled', () => { + let browser: Browser + let page: Page + + beforeEach(async () => { + browser = await chromium.launch() + page = await browser.newPage() + }) + + afterEach(async () => { + await browser.close() + }) + + test('Prints a message listing emulated features on server start', async () => { + const fixture = new Fixture() + .withFile( + 'vite.config.js', + `import { defineConfig } from 'vite'; import netlify from '@netlify/vite-plugin'; export default defineConfig({ plugins: [ netlify({ - middleware: true - }) + middleware: true, + edgeFunctions: { enabled: false }, + }) ] });`, + ) + .withFile( + 'index.html', + ` + + Hello World +

Hello from the browser

+ `, + ) + const directory = await fixture.create() + await fixture + .withPackages({ + vite: viteVersion, + '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + }) + .create() + + const mockLogger = createMockViteLogger() + const { server } = await startTestServer({ + root: directory, + logLevel: 'info', + customLogger: mockLogger, + }) + + expect(mockLogger.error).not.toHaveBeenCalled() + expect(mockLogger.warn).not.toHaveBeenCalled() + expect(mockLogger.warnOnce).not.toHaveBeenCalled() + expect(mockLogger.info).toHaveBeenCalledTimes(3) + expect(mockLogger.info).toHaveBeenNthCalledWith(1, 'Environment loaded', expect.objectContaining({})) + expect(mockLogger.info).toHaveBeenNthCalledWith( + 2, + 'Middleware loaded. Emulating features: blobs, environmentVariables, functions, headers, images, redirects, static.', + expect.objectContaining({}), ) - .withFile( - 'index.html', - ` - - Hello World - Hello from the static index.html file - - `, + expect(mockLogger.info).toHaveBeenNthCalledWith( + 3, + expect.stringContaining('Linking this project to a Netlify site'), + expect.objectContaining({}), ) - .withFile( - 'contact/email.html', - ` + + await server.close() + await fixture.destroy() + }) + + test('Returns static files from project dir', async () => { + const fixture = new Fixture() + .withFile( + 'vite.config.js', + `import { defineConfig } from 'vite'; + import netlify from '@netlify/vite-plugin'; + + export default defineConfig({ + plugins: [ + netlify({ + middleware: true + }) + ] + });`, + ) + .withFile( + 'index.html', + ` + + Hello World + Hello from the static index.html file + + `, + ) + .withFile( + 'contact/email.html', + ` Contact us via email Hello from another static file `, - ) - .withFile('js/main.js', `console.log('Hello from the browser')`) - const directory = await fixture.create() - await fixture - .withPackages({ - vite: '6.0.0', - '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + ) + .withFile('js/main.js', `console.log('Hello from the browser')`) + const directory = await fixture.create() + await fixture + .withPackages({ + vite: viteVersion, + '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + }) + .create() + + const { server, url } = await startTestServer({ + root: directory, }) - .create() - const { server, url } = await startTestServer({ - root: directory, - }) + const response = await page.goto(url) + expect(response?.status()).toBe(200) + expect(await response?.text()).toContain('Hello from the static index.html file') - const response = await page.goto(url) - expect(response?.status()).toBe(200) - expect(await response?.text()).toContain('Hello from the static index.html file') + expect(await page.goto(`${url}/js/main.js`).then((r) => r?.text())).toContain('console.log(') - expect(await page.goto(`${url}/js/main.js`).then((r) => r?.text())).toContain('console.log(') - - expect(await page.goto(`${url}/contact/email.html`).then((r) => r?.text())).toContain( - 'Hello from another static file', - ) + expect(await page.goto(`${url}/contact/email.html`).then((r) => r?.text())).toContain( + 'Hello from another static file', + ) - // This is Vite's behavior in dev for "404s" - const notFoundResponse = await page.goto(`${url}/wp-admin.php`) - expect(notFoundResponse?.status()).toBe(200) - expect(await notFoundResponse?.text()).toContain('Hello from the static index.html file') + // This is Vite's behavior in dev for "404s" + const notFoundResponse = await page.goto(`${url}/wp-admin.php`) + expect(notFoundResponse?.status()).toBe(200) + expect(await notFoundResponse?.text()).toContain('Hello from the static index.html file') - await server.close() - await fixture.destroy() - }) + await server.close() + await fixture.destroy() + }) - test('Returns static files with configured Netlify headers', async () => { - const fixture = new Fixture() - .withFile( - 'netlify.toml', - `[build] + test('Returns static files with configured Netlify headers', async () => { + const fixture = new Fixture() + .withFile( + 'netlify.toml', + `[build] publish = "dist" [[headers]] for = "/contact/*" @@ -312,10 +313,10 @@ defined on your team and site and much more. Run npx netlify init to get started for = "/*" [headers.values] "X-NF-Hello" = "world"`, - ) - .withFile( - 'vite.config.js', - `import { defineConfig } from 'vite'; + ) + .withFile( + 'vite.config.js', + `import { defineConfig } from 'vite'; import netlify from '@netlify/vite-plugin'; export default defineConfig({ @@ -325,63 +326,63 @@ defined on your team and site and much more. Run npx netlify init to get started }) ] });`, - ) - .withFile( - 'index.html', - ` - - Hello World - Hello from the static index.html file - `, - ) - .withFile( - 'contact/email.html', - ` + ) + .withFile( + 'index.html', + ` + + Hello World + Hello from the static index.html file + `, + ) + .withFile( + 'contact/email.html', + ` Contact us via email Hello from another static file `, - ) - const directory = await fixture.create() - await fixture - .withPackages({ - vite: '6.0.0', - '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + ) + const directory = await fixture.create() + await fixture + .withPackages({ + vite: viteVersion, + '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + }) + .create() + + const { server, url } = await startTestServer({ + root: directory, }) - .create() - - const { server, url } = await startTestServer({ - root: directory, - }) - expect(await page.goto(`${url}/contact/email`).then((r) => r?.headers())).toHaveProperty('x-nf-hello', 'world') - expect(await page.goto(url).then((r) => r?.headers())).toHaveProperty('x-nf-hello', 'world') - expect(await page.goto(`${url}/contact/email`).then((r) => r?.headers())).toHaveProperty( - 'x-contact-type', - 'email', - ) - expect(await page.goto(url).then((r) => r?.headers())).not.toHaveProperty('x-contact-type') + expect(await page.goto(`${url}/contact/email`).then((r) => r?.headers())).toHaveProperty('x-nf-hello', 'world') + expect(await page.goto(url).then((r) => r?.headers())).toHaveProperty('x-nf-hello', 'world') + expect(await page.goto(`${url}/contact/email`).then((r) => r?.headers())).toHaveProperty( + 'x-contact-type', + 'email', + ) + expect(await page.goto(url).then((r) => r?.headers())).not.toHaveProperty('x-contact-type') - await server.close() - await fixture.destroy() - }) + await server.close() + await fixture.destroy() + }) - test('Respects configured Netlify redirects and rewrites', async () => { - const fixture = new Fixture() - .withFile( - 'netlify.toml', - `[[redirects]] - status = 301 - from = "/contact/e-mail" - to = "/contact/email" - [[redirects]] - status = 200 - from = "/beta/*" - to = "/nextgenv3/:splat"`, - ) - .withFile( - 'vite.config.js', - `import { defineConfig } from 'vite'; + test('Respects configured Netlify redirects and rewrites', async () => { + const fixture = new Fixture() + .withFile( + 'netlify.toml', + `[[redirects]] + status = 301 + from = "/contact/e-mail" + to = "/contact/email" + [[redirects]] + status = 200 + from = "/beta/*" + to = "/nextgenv3/:splat"`, + ) + .withFile( + 'vite.config.js', + `import { defineConfig } from 'vite'; import netlify from '@netlify/vite-plugin'; export default defineConfig({ @@ -391,81 +392,83 @@ defined on your team and site and much more. Run npx netlify init to get started }) ] });`, - ) - .withFile( - 'contact/email.html', - ` + ) + .withFile( + 'contact/email.html', + ` Contact us via email Hello from the redirect target `, - ) - .withFile( - 'nextgenv3/pricing.html', - ` + ) + .withFile( + 'nextgenv3/pricing.html', + ` Pricing Hello from the rewrite target `, - ) - const directory = await fixture.create() - await fixture - .withPackages({ - vite: '6.0.0', - '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + ) + const directory = await fixture.create() + await fixture + .withPackages({ + vite: viteVersion, + '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + }) + .create() + + const { server, url } = await startTestServer({ + root: directory, }) - .create() - - const { server, url } = await startTestServer({ - root: directory, - }) - - expect(await page.goto(`${url}/contact/email`).then((r) => r?.text())).toContain('Hello from the redirect target') - expect(await page.goto(`${url}/contact/e-mail`).then((r) => r?.text())).toContain( - 'Hello from the redirect target', - ) - expect(await page.goto(`${url}/beta/pricing`).then((r) => r?.text())).toContain('Hello from the rewrite target') - await server.close() - await fixture.destroy() - }) - - test('Handles Image CDN requests', async () => { - const IMAGE_WIDTH = 800 - const IMAGE_HEIGHT = 400 + expect(await page.goto(`${url}/contact/email`).then((r) => r?.text())).toContain( + 'Hello from the redirect target', + ) + expect(await page.goto(`${url}/contact/e-mail`).then((r) => r?.text())).toContain( + 'Hello from the redirect target', + ) + expect(await page.goto(`${url}/beta/pricing`).then((r) => r?.text())).toContain('Hello from the rewrite target') - const remoteServer = new HTTPServer( - createImageServerHandler(() => { - return { width: IMAGE_WIDTH, height: IMAGE_HEIGHT } - }), - ) + await server.close() + await fixture.destroy() + }) - const remoteServerAddress = await remoteServer.start() + test('Handles Image CDN requests', async () => { + const IMAGE_WIDTH = 800 + const IMAGE_HEIGHT = 400 - const fixture = new Fixture() - .withFile( - 'netlify.toml', - `[images] - remote_images = [ - "^${remoteServerAddress}/allowed/.*" - ]`, + const remoteServer = new HTTPServer( + createImageServerHandler(() => { + return { width: IMAGE_WIDTH, height: IMAGE_HEIGHT } + }), ) - .withFile( - 'vite.config.js', - `import { defineConfig } from 'vite'; - import netlify from '@netlify/vite-plugin'; - export default defineConfig({ - plugins: [ - netlify({ - middleware: true, - }) - ] - });`, - ) - .withFile( - 'index.html', - ` + const remoteServerAddress = await remoteServer.start() + + const fixture = new Fixture() + .withFile( + 'netlify.toml', + `[images] + remote_images = [ + "^${remoteServerAddress}/allowed/.*" + ]`, + ) + .withFile( + 'vite.config.js', + `import { defineConfig } from 'vite'; + import netlify from '@netlify/vite-plugin'; + + export default defineConfig({ + plugins: [ + netlify({ + middleware: true, + }) + ] + });`, + ) + .withFile( + 'index.html', + ` Hello World @@ -475,165 +478,166 @@ defined on your team and site and much more. Run npx netlify init to get started `, - ) - .withFile('local/image.jpg', await generateImage(IMAGE_WIDTH, IMAGE_HEIGHT)) - - const directory = await fixture.create() - await fixture - .withPackages({ - vite: '6.0.0', - '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + ) + .withFile('local/image.jpg', await generateImage(IMAGE_WIDTH, IMAGE_HEIGHT)) + + const directory = await fixture.create() + await fixture + .withPackages({ + vite: viteVersion, + '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + }) + .create() + + const mockLogger = createMockViteLogger() + const { server, url } = await startTestServer({ + root: directory, + logLevel: 'info', + customLogger: mockLogger, }) - .create() - const mockLogger = createMockViteLogger() - const { server, url } = await startTestServer({ - root: directory, - logLevel: 'info', - customLogger: mockLogger, - }) - - await page.goto(url) + await page.goto(url) - const getImageSize = (locator: Locator) => { - return locator.evaluate((img: HTMLImageElement) => { - if (img.naturalWidth === 0 || img.naturalHeight === 0) { - throw new Error(`Image was not loaded`) - } + const getImageSize = (locator: Locator) => { + return locator.evaluate((img: HTMLImageElement) => { + if (img.naturalWidth === 0 || img.naturalHeight === 0) { + throw new Error(`Image was not loaded`) + } - return { - width: img.naturalWidth, - height: img.naturalHeight, - } - }) - } + return { + width: img.naturalWidth, + height: img.naturalHeight, + } + }) + } - expect(await getImageSize(page.locator('#local-image'))).toEqual({ width: 100, height: 50 }) - expect(await getImageSize(page.locator('#allowed-remote-image'))).toEqual({ width: 100, height: 50 }) + expect(await getImageSize(page.locator('#local-image'))).toEqual({ width: 100, height: 50 }) + expect(await getImageSize(page.locator('#allowed-remote-image'))).toEqual({ width: 100, height: 50 }) - await expect( - async () => await getImageSize(page.locator('#not-allowed-remote-image')), - 'Not allowed remote image should not load', - ).rejects.toThrowError(`Image was not loaded`) + await expect( + async () => await getImageSize(page.locator('#not-allowed-remote-image')), + 'Not allowed remote image should not load', + ).rejects.toThrowError(`Image was not loaded`) - await server.close() - await fixture.destroy() + await server.close() + await fixture.destroy() + }) }) - }) - describe('With @vitejs/plugin-react', () => { - // TODO(serhalp): Skipping on Windows for now. There's an issue on the GitHub Actions - // Windows image with resolving the `src/main.jsx` path for some reason. - test.skipIf(process.platform === 'win32')('Returns static files with configured Netlify headers', async () => { - const fixture = new Fixture() - .withFile( - 'vite.config.js', - `import { defineConfig } from 'vite'; - import netlify from '@netlify/vite-plugin'; - import react from '@vitejs/plugin-react'; + describe('With @vitejs/plugin-react', () => { + // TODO(serhalp): Skipping on Windows for now. There's an issue on the GitHub Actions + // Windows image with resolving the `src/main.jsx` path for some reason. + test.skipIf(process.platform === 'win32')('Returns static files with configured Netlify headers', async () => { + const fixture = new Fixture() + .withFile( + 'vite.config.js', + `import { defineConfig } from 'vite'; + import netlify from '@netlify/vite-plugin'; + import react from '@vitejs/plugin-react'; - export default defineConfig({ - plugins: [ - netlify({ - middleware: true - }), - react(), - ] - });`, - ) - .withFile( - 'netlify.toml', - `[[headers]] - for = "/" - [headers.values] - "X-NF-Hello" = "world"`, - ) - .withFile( - 'index.html', - ` - - - - Hello from SSR - - -
- - - `, - ) - .withFile( - 'src/main.jsx', - `import { StrictMode } from 'react' - import { createRoot } from 'react-dom/client' - import App from './App.jsx' - import './index.css' - - createRoot(document.getElementById('root')).render( - - - , - )`, - ) - .withFile('src/index.css', 'body { color: red }') - .withFile( - 'src/App.jsx', - `import reactLogo from './assets/react.svg' - - export default () => -
-

Hello from CSR

- -
`, - ) - .withFile( - 'src/assets/react.svg', - '', - ) - const directory = await fixture.create() - await fixture - .withPackages({ - '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), - '@vitejs/plugin-react': '4.5.0', - react: '19.1.0', - 'react-dom': '19.1.0', - vite: '6.3.5', + export default defineConfig({ + plugins: [ + netlify({ + middleware: true + }), + react(), + ] + });`, + ) + .withFile( + 'netlify.toml', + `[[headers]] + for = "/" + [headers.values] + "X-NF-Hello" = "world"`, + ) + .withFile( + 'index.html', + ` + + + + Hello from SSR + + +
+ + + `, + ) + .withFile( + 'src/main.jsx', + `import { StrictMode } from 'react' + import { createRoot } from 'react-dom/client' + import App from './App.jsx' + import './index.css' + + createRoot(document.getElementById('root')).render( + + + , + )`, + ) + .withFile('src/index.css', 'body { color: red }') + .withFile( + 'src/App.jsx', + `import reactLogo from './assets/react.svg' + + export default () => +
+

Hello from CSR

+ +
`, + ) + .withFile( + 'src/assets/react.svg', + '', + ) + const directory = await fixture.create() + await fixture + .withPackages({ + '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + '@vitejs/plugin-react': '4.5.0', + react: '19.1.0', + 'react-dom': '19.1.0', + vite: viteVersion, + }) + .create() + + const { server, url } = await startTestServer({ + root: directory, }) - .create() - const { server, url } = await startTestServer({ - root: directory, - }) + const browser = await chromium.launch() + const page = await browser.newPage() + const browserErrorLogs: ConsoleMessage[] = [] + page.on('console', (msg) => { + if (msg.type() === 'error') { + browserErrorLogs.push(msg) + } + }) - const browser = await chromium.launch() - const page = await browser.newPage() - const browserErrorLogs: ConsoleMessage[] = [] - page.on('console', (msg) => { - if (msg.type() === 'error') { - browserErrorLogs.push(msg) - } + const response = await page.goto(url) + expect(response?.status()).toBe(200) + expect(await response?.text()).toContain('Hello from SSR') + expect(response?.headers()).toHaveProperty('x-nf-hello', 'world') + expect(await page.innerHTML('html')).toContain('Hello from CSR') + + // React SPA mode serves index.html for unknown routes + const notFoundResponse = await page.goto(`${url}/wp-admin.php`) + expect(notFoundResponse?.status()).toBe(200) + expect(await notFoundResponse?.text()).toContain('Hello from SSR') + expect(await page.innerHTML('html')).toContain('Hello from CSR') + + expect( + browserErrorLogs, + `Unexpected error logs in browser: ${JSON.stringify(browserErrorLogs, null, 2)}`, + ).toHaveLength(0) + + await server.close() + await fixture.destroy() + await browser.close() }) - - const response = await page.goto(url) - expect(response?.status()).toBe(200) - expect(await response?.text()).toContain('Hello from SSR') - expect(response?.headers()).toHaveProperty('x-nf-hello', 'world') - expect(await page.innerHTML('html')).toContain('Hello from CSR') - - // React SPA mode serves index.html for unknown routes - const notFoundResponse = await page.goto(`${url}/wp-admin.php`) - expect(notFoundResponse?.status()).toBe(200) - expect(await notFoundResponse?.text()).toContain('Hello from SSR') - expect(await page.innerHTML('html')).toContain('Hello from CSR') - - expect( - browserErrorLogs, - `Unexpected error logs in browser: ${JSON.stringify(browserErrorLogs, null, 2)}`, - ).toHaveLength(0) - - await server.close() - await fixture.destroy() - await browser.close() }) }) })