diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index 5cbe0a5c6bbd..a8b9bca7ab39 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -33,10 +33,11 @@ "build": "astro-scripts build \"src/**/*.ts\" && tsc", "build:ci": "astro-scripts build \"src/**/*.ts\"", "test": "pnpm run test-fn && pnpm run test-static && pnpm run test:dev", - "test-fn": "astro-scripts test \"test/functions/*.test.js\"", - "test:dev": "astro-scripts test \"test/development/*.test.js\"", - "test-static": "astro-scripts test \"test/static/*.test.js\"", - "test:hosted": "astro-scripts test \"test/hosted/*.test.js\"" + "test-fn": "astro-scripts test \"test/functions/*.test.ts\"", + "test:dev": "astro-scripts test \"test/development/*.test.ts\"", + "test-static": "astro-scripts test \"test/static/*.test.ts\"", + "test:hosted": "astro-scripts test \"test/hosted/*.test.js\"", + "typecheck:tests": "tsc --build tsconfig.test.json" }, "dependencies": { "@astrojs/internal-helpers": "workspace:*", diff --git a/packages/integrations/netlify/test/development/primitives.test.js b/packages/integrations/netlify/test/development/primitives.test.ts similarity index 88% rename from packages/integrations/netlify/test/development/primitives.test.js rename to packages/integrations/netlify/test/development/primitives.test.ts index 016305e42cae..a8fa8ae4db67 100644 --- a/packages/integrations/netlify/test/development/primitives.test.js +++ b/packages/integrations/netlify/test/development/primitives.test.ts @@ -2,14 +2,13 @@ import assert from 'node:assert/strict'; import { after, afterEach, before, describe, it } from 'node:test'; import * as cheerio from 'cheerio'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; +import { type DevServer, type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; import netlifyAdapter from '../../dist/index.js'; describe('Netlify primitives', () => { describe('Development', () => { - /** @type {import('../../../../astro/test/test-utils').Fixture} */ - let fixture; - let devServer; + let fixture: Fixture; + let devServer: DevServer; before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/primitives/', import.meta.url), @@ -65,9 +64,11 @@ describe('Netlify primitives', () => { it('loads images in development', async () => { const imgResponse = await fixture.fetch('/astronaut'); const $img = cheerio.load(await imgResponse.text()); - const images = $img('img').map((_i, el) => { - return $img(el).attr('src'); - }); + const images = $img('img') + .map((_i, el) => { + return $img(el).attr('src'); + }) + .toArray(); for (const imgSrc of images) { assert(imgSrc.startsWith('/.netlify/images')); @@ -89,9 +90,11 @@ describe('Netlify primitives', () => { try { const imgResponse = await cdnDisabledFixture.fetch('/astronaut'); const $img = cheerio.load(await imgResponse.text()); - const images = $img('img').map((_i, el) => { - return $img(el).attr('src'); - }); + const images = $img('img') + .map((_i, el) => { + return $img(el).attr('src'); + }) + .toArray(); for (const imgSrc of images) { assert( @@ -101,7 +104,7 @@ describe('Netlify primitives', () => { } } finally { await cdnDisabledServer.stop(); - process.env.DISABLE_IMAGE_CDN = undefined; + delete process.env.DISABLE_IMAGE_CDN; } }); }); diff --git a/packages/integrations/netlify/test/functions/cookies.test.js b/packages/integrations/netlify/test/functions/cookies.test.js deleted file mode 100644 index 6ef16763ed5d..000000000000 --- a/packages/integrations/netlify/test/functions/cookies.test.js +++ /dev/null @@ -1,65 +0,0 @@ -import * as assert from 'node:assert/strict'; -import { before, describe, it } from 'node:test'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; - -describe( - 'Cookies', - () => { - let fixture; - - before(async () => { - fixture = await loadFixture({ root: new URL('./fixtures/cookies/', import.meta.url) }); - await fixture.build(); - }); - - it('Can set multiple', async () => { - const entryURL = new URL( - './fixtures/cookies/.netlify/v1/functions/ssr/ssr.mjs', - import.meta.url, - ); - const { default: handler } = await import(entryURL); - const resp = await handler( - new Request('http://example.com/login', { method: 'POST', body: '{}' }), - {}, - ); - assert.equal(resp.status, 301); - assert.equal(resp.headers.get('location'), '/'); - assert.deepEqual(resp.headers.getSetCookie(), ['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); - }); - - it('Can set partitioned cookie', async () => { - const entryURL = new URL( - './fixtures/cookies/.netlify/v1/functions/ssr/ssr.mjs', - import.meta.url, - ); - const { default: handler } = await import(entryURL); - const resp = await handler(new Request('http://example.com/partitioned'), {}); - assert.equal(resp.status, 200); - const cookie = resp.headers.getSetCookie()[0]; - assert.ok(cookie.includes('Partitioned'), 'Cookie should include Partitioned attribute'); - }); - - it('renders dynamic 404 page', async () => { - const entryURL = new URL( - './fixtures/cookies/.netlify/v1/functions/ssr/ssr.mjs', - import.meta.url, - ); - const { default: handler } = await import(entryURL); - const resp = await handler( - new Request('http://example.com/nonexistant-page', { - headers: { - 'x-test': 'bar', - }, - }), - {}, - ); - assert.equal(resp.status, 404); - const text = await resp.text(); - assert.equal(text.includes('This is my custom 404 page'), true); - assert.equal(text.includes('x-test: bar'), true); - }); - }, - { - timeout: 120000, - }, -); diff --git a/packages/integrations/netlify/test/functions/cookies.test.ts b/packages/integrations/netlify/test/functions/cookies.test.ts new file mode 100644 index 000000000000..d3c0f7d434db --- /dev/null +++ b/packages/integrations/netlify/test/functions/cookies.test.ts @@ -0,0 +1,59 @@ +import * as assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; +import { type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; + +describe('Cookies', { timeout: 120000 }, () => { + let fixture: Fixture; + + before(async () => { + fixture = await loadFixture({ root: new URL('./fixtures/cookies/', import.meta.url) }); + await fixture.build({}); + }); + + it('Can set multiple', async () => { + const entryURL = new URL( + './fixtures/cookies/.netlify/v1/functions/ssr/ssr.mjs', + import.meta.url, + ); + const { default: handler } = await import(entryURL.href); + const resp = await handler( + new Request('http://example.com/login', { method: 'POST', body: '{}' }), + {}, + ); + assert.equal(resp.status, 301); + assert.equal(resp.headers.get('location'), '/'); + assert.deepEqual(resp.headers.getSetCookie(), ['foo=foo; HttpOnly', 'bar=bar; HttpOnly']); + }); + + it('Can set partitioned cookie', async () => { + const entryURL = new URL( + './fixtures/cookies/.netlify/v1/functions/ssr/ssr.mjs', + import.meta.url, + ); + const { default: handler } = await import(entryURL.href); + const resp = await handler(new Request('http://example.com/partitioned'), {}); + assert.equal(resp.status, 200); + const cookie = resp.headers.getSetCookie()[0]; + assert.ok(cookie.includes('Partitioned'), 'Cookie should include Partitioned attribute'); + }); + + it('renders dynamic 404 page', async () => { + const entryURL = new URL( + './fixtures/cookies/.netlify/v1/functions/ssr/ssr.mjs', + import.meta.url, + ); + const { default: handler } = await import(entryURL.href); + const resp = await handler( + new Request('http://example.com/nonexistant-page', { + headers: { + 'x-test': 'bar', + }, + }), + {}, + ); + assert.equal(resp.status, 404); + const text = await resp.text(); + assert.equal(text.includes('This is my custom 404 page'), true); + assert.equal(text.includes('x-test: bar'), true); + }); +}); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.js b/packages/integrations/netlify/test/functions/edge-middleware.test.js deleted file mode 100644 index 6d255f4dc7f9..000000000000 --- a/packages/integrations/netlify/test/functions/edge-middleware.test.js +++ /dev/null @@ -1,66 +0,0 @@ -import * as assert from 'node:assert/strict'; -import { after, before, describe, it } from 'node:test'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; - -describe( - 'Middleware', - () => { - const root = new URL('./fixtures/middleware/', import.meta.url); - - describe('middlewareMode: classic', () => { - let fixture; - before(async () => { - process.env.EDGE_MIDDLEWARE = 'false'; - fixture = await loadFixture({ root }); - await fixture.build(); - }); - - it('emits no edge function', async () => { - assert.equal( - fixture.pathExists('../.netlify/v1/edge-functions/middleware/middleware.mjs'), - false, - ); - }); - - it('applies middleware to static files at build-time', async () => { - // prerendered page has middleware applied at build time - const prerenderedPage = await fixture.readFile('prerender/index.html'); - assert.equal(prerenderedPage.includes('Middleware'), true); - }); - - after(async () => { - process.env.EDGE_MIDDLEWARE = undefined; - await fixture.clean(); - }); - }); - - describe('middlewareMode: edge', () => { - let fixture; - before(async () => { - process.env.EDGE_MIDDLEWARE = 'true'; - fixture = await loadFixture({ root }); - await fixture.build(); - }); - - it('emits an edge function', async () => { - const contents = await fixture.readFile( - '../.netlify/v1/edge-functions/middleware/middleware.mjs', - ); - assert.equal(contents.includes('"Hello world"'), false); - }); - - it.skip('does not apply middleware during prerendering', async () => { - const prerenderedPage = await fixture.readFile('prerender/index.html'); - assert.equal(prerenderedPage.includes(''), true); - }); - - after(async () => { - process.env.EDGE_MIDDLEWARE = undefined; - await fixture.clean(); - }); - }); - }, - { - timeout: 120000, - }, -); diff --git a/packages/integrations/netlify/test/functions/edge-middleware.test.ts b/packages/integrations/netlify/test/functions/edge-middleware.test.ts new file mode 100644 index 000000000000..e1a667c83924 --- /dev/null +++ b/packages/integrations/netlify/test/functions/edge-middleware.test.ts @@ -0,0 +1,60 @@ +import * as assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import { type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; + +describe('Middleware', { timeout: 120000 }, () => { + const root = new URL('./fixtures/middleware/', import.meta.url); + + describe('middlewareMode: classic', () => { + let fixture: Fixture; + before(async () => { + process.env.EDGE_MIDDLEWARE = 'false'; + fixture = await loadFixture({ root }); + await fixture.build({}); + }); + + it('emits no edge function', async () => { + assert.equal( + fixture.pathExists('../.netlify/v1/edge-functions/middleware/middleware.mjs'), + false, + ); + }); + + it('applies middleware to static files at build-time', async () => { + // prerendered page has middleware applied at build time + const prerenderedPage = await fixture.readFile('prerender/index.html'); + assert.equal(prerenderedPage.includes('Middleware'), true); + }); + + after(async () => { + delete process.env.EDGE_MIDDLEWARE; + await fixture.clean(); + }); + }); + + describe('middlewareMode: edge', () => { + let fixture: Fixture; + before(async () => { + process.env.EDGE_MIDDLEWARE = 'true'; + fixture = await loadFixture({ root }); + await fixture.build({}); + }); + + it('emits an edge function', async () => { + const contents = await fixture.readFile( + '../.netlify/v1/edge-functions/middleware/middleware.mjs', + ); + assert.equal(contents.includes('"Hello world"'), false); + }); + + it.skip('does not apply middleware during prerendering', async () => { + const prerenderedPage = await fixture.readFile('prerender/index.html'); + assert.equal(prerenderedPage.includes(''), true); + }); + + after(async () => { + delete process.env.EDGE_MIDDLEWARE; + await fixture.clean(); + }); + }); +}); diff --git a/packages/integrations/netlify/test/functions/image-cdn.test.js b/packages/integrations/netlify/test/functions/image-cdn.test.js deleted file mode 100644 index 8d6196817607..000000000000 --- a/packages/integrations/netlify/test/functions/image-cdn.test.js +++ /dev/null @@ -1,182 +0,0 @@ -import * as assert from 'node:assert/strict'; -import { after, before, describe, it } from 'node:test'; -import { remotePatternToRegex } from '@astrojs/netlify'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; -import imageService from '../../dist/image-service.js'; - -describe( - 'Image CDN', - () => { - const root = new URL('./fixtures/middleware/', import.meta.url); - - describe('configuration', () => { - after(() => { - process.env.DISABLE_IMAGE_CDN = undefined; - }); - - it('enables Netlify Image CDN', async () => { - const fixture = await loadFixture({ root }); - await fixture.build(); - - const astronautPage = await fixture.readFile('astronaut/index.html'); - assert.equal(astronautPage.includes(`src="/.netlify/image`), true); - }); - - it('respects image CDN opt-out', async () => { - process.env.DISABLE_IMAGE_CDN = 'true'; - const fixture = await loadFixture({ root }); - await fixture.build(); - - const astronautPage = await fixture.readFile('astronaut/index.html'); - assert.equal(astronautPage.includes(`src="/_astro/astronaut.`), true); - }); - }); - - describe('remote image config', () => { - let regexes; - - before(async () => { - const fixture = await loadFixture({ root }); - await fixture.build(); - - const config = await fixture.readFile('../.netlify/v1/config.json'); - if (config) { - regexes = JSON.parse(config).images.remote_images.map((pattern) => new RegExp(pattern)); - } - }); - - it('generates remote image config patterns', async () => { - assert.equal(regexes?.length, 3); - }); - - it('generates correct config for domains', async () => { - const domain = regexes[0]; - assert.equal(domain.test('https://example.net/image.jpg'), true); - assert.equal( - domain.test('https://www.example.net/image.jpg'), - false, - 'subdomain should not match', - ); - assert.equal(domain.test('http://example.net/image.jpg'), true, 'http should match'); - assert.equal( - domain.test('https://example.net/subdomain/image.jpg'), - true, - 'subpath should match', - ); - const subdomain = regexes[1]; - assert.equal( - subdomain.test('https://secret.example.edu/image.jpg'), - true, - 'should match subdomains', - ); - assert.equal( - subdomain.test('https://secretxexample.edu/image.jpg'), - false, - 'should not use dots in domains as wildcards', - ); - }); - - it('generates correct config for remotePatterns', async () => { - const patterns = regexes[2]; - assert.equal( - patterns.test('https://example.org/images/1.jpg'), - true, - 'should match domain', - ); - assert.equal( - patterns.test('https://www.example.org/images/2.jpg'), - true, - 'www subdomain should match', - ); - assert.equal( - patterns.test('https://www.subdomain.example.org/images/2.jpg'), - false, - 'second level subdomain should not match', - ); - assert.equal( - patterns.test('https://example.org/not-images/2.jpg'), - false, - 'wrong path should not match', - ); - }); - - it('warns when remotepatterns generates an invalid regex', async (t) => { - const logger = { - warn: t.mock.fn(), - }; - const regex = remotePatternToRegex( - { - hostname: '*.examp[le.org', - pathname: '/images/*', - }, - logger, - ); - assert.strictEqual(regex, undefined); - const calls = logger.warn.mock.calls; - assert.strictEqual(calls.length, 1); - assert.equal( - calls[0].arguments[0], - 'Could not generate a valid regex from the remotePattern "{"hostname":"*.examp[le.org","pathname":"/images/*"}". Please check the syntax.', - ); - }); - }); - - describe('fit parameter', () => { - it('includes fit parameter in image URL', () => { - const url = imageService.getURL({ - src: 'images/astronaut.jpg', - width: 300, - height: 400, - fit: 'cover', - format: 'webp', - }); - assert.ok(url.includes('fit=cover'), `Expected fit=cover in URL, got: ${url}`); - }); - - it('maps Astro fit values to Netlify equivalents', () => { - const cases = [ - ['contain', 'contain'], - ['cover', 'cover'], - ['fill', 'fill'], - ['inside', 'contain'], - ['outside', 'cover'], - ['scale-down', 'contain'], - ]; - for (const [astroFit, netlifyFit] of cases) { - const url = imageService.getURL({ - src: 'img.jpg', - width: 100, - height: 100, - fit: astroFit, - }); - assert.ok( - url.includes(`fit=${netlifyFit}`), - `Expected fit=${netlifyFit} for astro fit="${astroFit}", got: ${url}`, - ); - } - }); - - it('omits fit parameter when fit is none or unset', () => { - const withNone = imageService.getURL({ - src: 'img.jpg', - width: 100, - height: 100, - fit: 'none', - }); - assert.ok( - !withNone.includes('fit='), - `Expected no fit param for fit="none", got: ${withNone}`, - ); - - const withoutFit = imageService.getURL({ src: 'img.jpg', width: 100, height: 100 }); - assert.ok( - !withoutFit.includes('fit='), - `Expected no fit param when unset, got: ${withoutFit}`, - ); - }); - }); - }, - { - timeout: 120000, - }, -); diff --git a/packages/integrations/netlify/test/functions/image-cdn.test.ts b/packages/integrations/netlify/test/functions/image-cdn.test.ts new file mode 100644 index 000000000000..4b8ed3783568 --- /dev/null +++ b/packages/integrations/netlify/test/functions/image-cdn.test.ts @@ -0,0 +1,179 @@ +import * as assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import type { AstroIntegrationLogger } from 'astro'; +import { remotePatternToRegex } from '@astrojs/netlify'; +import { loadFixture } from '../../../../astro/test/test-utils.js'; +import imageService from '../../dist/image-service.js'; + +describe('Image CDN', { timeout: 120000 }, () => { + const root = new URL('./fixtures/middleware/', import.meta.url); + + describe('configuration', () => { + after(() => { + delete process.env.DISABLE_IMAGE_CDN; + }); + + it('enables Netlify Image CDN', async () => { + const fixture = await loadFixture({ root }); + await fixture.build({}); + + const astronautPage = await fixture.readFile('astronaut/index.html'); + assert.equal(astronautPage.includes(`src="/.netlify/image`), true); + }); + + it('respects image CDN opt-out', async () => { + process.env.DISABLE_IMAGE_CDN = 'true'; + const fixture = await loadFixture({ root }); + await fixture.build({}); + + const astronautPage = await fixture.readFile('astronaut/index.html'); + assert.equal(astronautPage.includes(`src="/_astro/astronaut.`), true); + }); + }); + + describe('remote image config', () => { + let regexes: RegExp[] | undefined; + + before(async () => { + const fixture = await loadFixture({ root }); + await fixture.build({}); + + const config = await fixture.readFile('../.netlify/v1/config.json'); + if (config) { + regexes = JSON.parse(config).images.remote_images.map( + (pattern: string) => new RegExp(pattern), + ); + } + }); + + it('generates remote image config patterns', async () => { + assert.equal(regexes?.length, 3); + }); + + it('generates correct config for domains', async () => { + const domain = regexes![0]; + assert.equal(domain.test('https://example.net/image.jpg'), true); + assert.equal( + domain.test('https://www.example.net/image.jpg'), + false, + 'subdomain should not match', + ); + assert.equal(domain.test('http://example.net/image.jpg'), true, 'http should match'); + assert.equal( + domain.test('https://example.net/subdomain/image.jpg'), + true, + 'subpath should match', + ); + const subdomain = regexes![1]; + assert.equal( + subdomain.test('https://secret.example.edu/image.jpg'), + true, + 'should match subdomains', + ); + assert.equal( + subdomain.test('https://secretxexample.edu/image.jpg'), + false, + 'should not use dots in domains as wildcards', + ); + }); + + it('generates correct config for remotePatterns', async () => { + const patterns = regexes![2]; + assert.equal(patterns.test('https://example.org/images/1.jpg'), true, 'should match domain'); + assert.equal( + patterns.test('https://www.example.org/images/2.jpg'), + true, + 'www subdomain should match', + ); + assert.equal( + patterns.test('https://www.subdomain.example.org/images/2.jpg'), + false, + 'second level subdomain should not match', + ); + assert.equal( + patterns.test('https://example.org/not-images/2.jpg'), + false, + 'wrong path should not match', + ); + }); + + it('warns when remotepatterns generates an invalid regex', async (t) => { + const warnFn = t.mock.fn(); + const logger = { warn: warnFn } as unknown as AstroIntegrationLogger; + const regex = remotePatternToRegex( + { + hostname: '*.examp[le.org', + pathname: '/images/*', + }, + logger, + ); + assert.strictEqual(regex, undefined); + const calls = warnFn.mock.calls; + assert.strictEqual(calls.length, 1); + assert.equal( + calls[0].arguments[0], + 'Could not generate a valid regex from the remotePattern "{"hostname":"*.examp[le.org","pathname":"/images/*"}". Please check the syntax.', + ); + }); + }); + + describe('fit parameter', () => { + // imageService.getURL is typed as returning string | Promise, but this + // implementation always returns a string synchronously + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getURL = (options: any): string => imageService.getURL(options, {} as any) as string; + + it('includes fit parameter in image URL', () => { + const url = getURL({ + src: 'images/astronaut.jpg', + width: 300, + height: 400, + fit: 'cover', + format: 'webp', + }); + assert.ok(url.includes('fit=cover'), `Expected fit=cover in URL, got: ${url}`); + }); + + it('maps Astro fit values to Netlify equivalents', () => { + const cases = [ + ['contain', 'contain'], + ['cover', 'cover'], + ['fill', 'fill'], + ['inside', 'contain'], + ['outside', 'cover'], + ['scale-down', 'contain'], + ]; + for (const [astroFit, netlifyFit] of cases) { + const url = getURL({ + src: 'img.jpg', + width: 100, + height: 100, + fit: astroFit, + }); + assert.ok( + url.includes(`fit=${netlifyFit}`), + `Expected fit=${netlifyFit} for astro fit="${astroFit}", got: ${url}`, + ); + } + }); + + it('omits fit parameter when fit is none or unset', () => { + const withNone = getURL({ + src: 'img.jpg', + width: 100, + height: 100, + fit: 'none', + }); + assert.ok( + !withNone.includes('fit='), + `Expected no fit param for fit="none", got: ${withNone}`, + ); + + const withoutFit = getURL({ src: 'img.jpg', width: 100, height: 100 }); + assert.ok( + !withoutFit.includes('fit='), + `Expected no fit param when unset, got: ${withoutFit}`, + ); + }); + }); +}); diff --git a/packages/integrations/netlify/test/functions/include-files.test.js b/packages/integrations/netlify/test/functions/include-files.test.js deleted file mode 100644 index e54e116a78c3..000000000000 --- a/packages/integrations/netlify/test/functions/include-files.test.js +++ /dev/null @@ -1,184 +0,0 @@ -import * as assert from 'node:assert/strict'; -import { existsSync } from 'node:fs'; -import { after, before, describe, it } from 'node:test'; -import netlify from '@astrojs/netlify'; -import * as cheerio from 'cheerio'; -import { globSync } from 'tinyglobby'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; - -describe( - 'Included vite assets files', - () => { - let fixture; - - const root = new URL('./fixtures/includes/', import.meta.url); - const expectedCwd = new URL('.netlify/v1/functions/ssr/packages/integrations/netlify/', root); - - const expectedAssetsInclude = ['./*.json']; - const excludedAssets = ['./files/exclude-asset.json']; - - before(async () => { - fixture = await loadFixture({ - root, - vite: { - assetsInclude: expectedAssetsInclude, - }, - adapter: netlify({ - excludeFiles: excludedAssets, - }), - }); - await fixture.build(); - }); - - it('Emits vite assets files', async () => { - for (const pattern of expectedAssetsInclude) { - const files = globSync(pattern); - for (const file of files) { - assert.ok( - existsSync(new URL(file, expectedCwd)), - `Expected file ${pattern} to exist in build`, - ); - } - } - }); - - it('Does not include vite assets files when excluded', async () => { - for (const file of excludedAssets) { - assert.ok( - !existsSync(new URL(file, expectedCwd)), - `Expected file ${file} to not exist in build`, - ); - } - }); - - after(async () => { - await fixture.clean(); - }); - }, - { - timeout: 120000, - }, -); - -describe( - 'Included files', - () => { - let fixture; - - const root = new URL('./fixtures/includes/', import.meta.url); - const expectedCwd = new URL( - '.netlify/v1/functions/ssr/packages/integrations/netlify/test/functions/fixtures/includes/', - root, - ); - - const expectedFiles = [ - './files/include-this.txt', - './files/also-this.csv', - './files/subdirectory/and-this.csv', - ]; - - before(async () => { - fixture = await loadFixture({ - root, - adapter: netlify({ - includeFiles: expectedFiles, - }), - }); - await fixture.build(); - }); - - it('Emits include files', async () => { - for (const file of expectedFiles) { - assert.ok(existsSync(new URL(file, expectedCwd)), `Expected file ${file} to exist`); - } - }); - - it('Can load included files correctly', async () => { - const entryURL = new URL( - './fixtures/includes/.netlify/v1/functions/ssr/ssr.mjs', - import.meta.url, - ); - const { default: handler } = await import(entryURL); - const resp = await handler(new Request('http://example.com/?file=include-this.txt'), {}); - const html = await resp.text(); - const $ = cheerio.load(html); - assert.equal($('h1').text(), 'hello'); - }); - - it('Includes traced node modules with symlinks', async () => { - const expected = new URL( - '.netlify/v1/functions/ssr/node_modules/.pnpm/cowsay@1.6.0/node_modules/cowsay/cows/happy-whale.cow', - root, - ); - assert.ok(existsSync(expected, 'Expected excluded file to exist in default build')); - }); - - after(async () => { - await fixture.clean(); - }); - }, - { - timeout: 120000, - }, -); - -describe( - 'Excluded files', - () => { - let fixture; - - const root = new URL('./fixtures/includes/', import.meta.url); - const expectedCwd = new URL( - '.netlify/v1/functions/ssr/packages/integrations/netlify/test/functions/fixtures/includes/', - root, - ); - - const includeFiles = ['./files/**/*.txt']; - const excludedTxt = ['./files/subdirectory/not-this.txt', './files/subdirectory/or-this.txt']; - const excludeFiles = [...excludedTxt, '../../../../../../../node_modules/.pnpm/cowsay@*/**']; - - before(async () => { - fixture = await loadFixture({ - root, - adapter: netlify({ - includeFiles: includeFiles, - excludeFiles: excludeFiles, - }), - }); - await fixture.build(); - }); - - it('Excludes traced node modules', async () => { - const expected = new URL( - '.netlify/v1/functions/ssr/node_modules/.pnpm/cowsay@1.6.0/node_modules/cowsay/cows/happy-whale.cow', - root, - ); - assert.ok(!existsSync(expected), 'Expected excluded file to not exist in build'); - }); - - it('Does not include files when excluded', async () => { - for (const pattern of includeFiles) { - const files = globSync(pattern, { ignore: excludedTxt }); - for (const file of files) { - assert.ok( - existsSync(new URL(file, expectedCwd)), - `Expected file ${pattern} to exist in build`, - ); - } - } - for (const file of excludedTxt) { - assert.ok( - !existsSync(new URL(file, expectedCwd)), - `Expected file ${file} to not exist in build`, - ); - } - }); - - after(async () => { - await fixture.clean(); - }); - }, - { - timeout: 120000, - }, -); diff --git a/packages/integrations/netlify/test/functions/include-files.test.ts b/packages/integrations/netlify/test/functions/include-files.test.ts new file mode 100644 index 000000000000..487298342eb5 --- /dev/null +++ b/packages/integrations/netlify/test/functions/include-files.test.ts @@ -0,0 +1,166 @@ +import * as assert from 'node:assert/strict'; +import { existsSync } from 'node:fs'; +import { after, before, describe, it } from 'node:test'; +import netlify from '@astrojs/netlify'; +import * as cheerio from 'cheerio'; +import { globSync } from 'tinyglobby'; +import { type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; + +describe('Included vite assets files', { timeout: 120000 }, () => { + let fixture: Fixture; + + const root = new URL('./fixtures/includes/', import.meta.url); + const expectedCwd = new URL('.netlify/v1/functions/ssr/packages/integrations/netlify/', root); + + const expectedAssetsInclude = ['./*.json']; + const excludedAssets = ['./files/exclude-asset.json']; + + before(async () => { + fixture = await loadFixture({ + root, + vite: { + assetsInclude: expectedAssetsInclude, + }, + adapter: netlify({ + excludeFiles: excludedAssets, + }), + }); + await fixture.build({}); + }); + + it('Emits vite assets files', async () => { + for (const pattern of expectedAssetsInclude) { + const files = globSync(pattern); + for (const file of files) { + assert.ok( + existsSync(new URL(file, expectedCwd)), + `Expected file ${pattern} to exist in build`, + ); + } + } + }); + + it('Does not include vite assets files when excluded', async () => { + for (const file of excludedAssets) { + assert.ok( + !existsSync(new URL(file, expectedCwd)), + `Expected file ${file} to not exist in build`, + ); + } + }); + + after(async () => { + await fixture.clean(); + }); +}); + +describe('Included files', { timeout: 120000 }, () => { + let fixture: Fixture; + + const root = new URL('./fixtures/includes/', import.meta.url); + const expectedCwd = new URL( + '.netlify/v1/functions/ssr/packages/integrations/netlify/test/functions/fixtures/includes/', + root, + ); + + const expectedFiles = [ + './files/include-this.txt', + './files/also-this.csv', + './files/subdirectory/and-this.csv', + ]; + + before(async () => { + fixture = await loadFixture({ + root, + adapter: netlify({ + includeFiles: expectedFiles, + }), + }); + await fixture.build({}); + }); + + it('Emits include files', async () => { + for (const file of expectedFiles) { + assert.ok(existsSync(new URL(file, expectedCwd)), `Expected file ${file} to exist`); + } + }); + + it('Can load included files correctly', async () => { + const entryURL = new URL( + './fixtures/includes/.netlify/v1/functions/ssr/ssr.mjs', + import.meta.url, + ); + const { default: handler } = await import(entryURL.href); + const resp = await handler(new Request('http://example.com/?file=include-this.txt'), {}); + const html = await resp.text(); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'hello'); + }); + + it('Includes traced node modules with symlinks', async () => { + const expected = new URL( + '.netlify/v1/functions/ssr/node_modules/.pnpm/cowsay@1.6.0/node_modules/cowsay/cows/happy-whale.cow', + root, + ); + assert.ok(existsSync(expected), 'Expected excluded file to exist in default build'); + }); + + after(async () => { + await fixture.clean(); + }); +}); + +describe('Excluded files', { timeout: 120000 }, () => { + let fixture: Fixture; + + const root = new URL('./fixtures/includes/', import.meta.url); + const expectedCwd = new URL( + '.netlify/v1/functions/ssr/packages/integrations/netlify/test/functions/fixtures/includes/', + root, + ); + + const includeFiles = ['./files/**/*.txt']; + const excludedTxt = ['./files/subdirectory/not-this.txt', './files/subdirectory/or-this.txt']; + const excludeFiles = [...excludedTxt, '../../../../../../../node_modules/.pnpm/cowsay@*/**']; + + before(async () => { + fixture = await loadFixture({ + root, + adapter: netlify({ + includeFiles: includeFiles, + excludeFiles: excludeFiles, + }), + }); + await fixture.build({}); + }); + + it('Excludes traced node modules', async () => { + const expected = new URL( + '.netlify/v1/functions/ssr/node_modules/.pnpm/cowsay@1.6.0/node_modules/cowsay/cows/happy-whale.cow', + root, + ); + assert.ok(!existsSync(expected), 'Expected excluded file to not exist in build'); + }); + + it('Does not include files when excluded', async () => { + for (const pattern of includeFiles) { + const files = globSync(pattern, { ignore: excludedTxt }); + for (const file of files) { + assert.ok( + existsSync(new URL(file, expectedCwd)), + `Expected file ${pattern} to exist in build`, + ); + } + } + for (const file of excludedTxt) { + assert.ok( + !existsSync(new URL(file, expectedCwd)), + `Expected file ${file} to not exist in build`, + ); + } + }); + + after(async () => { + await fixture.clean(); + }); +}); diff --git a/packages/integrations/netlify/test/functions/redirects.test.js b/packages/integrations/netlify/test/functions/redirects.test.js deleted file mode 100644 index 2c55aecb9ec5..000000000000 --- a/packages/integrations/netlify/test/functions/redirects.test.js +++ /dev/null @@ -1,68 +0,0 @@ -import * as assert from 'node:assert/strict'; -import { createServer } from 'node:http'; -import { before, describe, it } from 'node:test'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; - -describe( - 'SSR - Redirects', - () => { - let fixture; - - before(async () => { - fixture = await loadFixture({ root: new URL('./fixtures/redirects/', import.meta.url) }); - await fixture.build(); - }); - - it('Creates a redirects file', async () => { - const redirects = await fixture.readFile('./_redirects'); - const parts = redirects.split(/\s+/); - // based on https://github.com/withastro/astro/issues/16030 for the default option `trailingSlash: 'ignore'` both variants should be generated - assert.deepEqual(parts, ['', '/other/', '/', '301', '/other', '/', '301', '']); - }); - - it('Does not create .html files', async () => { - let hasErrored = false; - try { - await fixture.readFile('/other/index.html'); - } catch { - hasErrored = true; - } - assert.equal(hasErrored, true, 'this file should not exist'); - }); - - it('renders static 404 page', async () => { - const entryURL = new URL( - './fixtures/redirects/.netlify/v1/functions/ssr/ssr.mjs', - import.meta.url, - ); - const { default: handler } = await import(entryURL); - const resp = await handler(new Request('http://example.com/nonexistant-page'), {}); - assert.equal(resp.status, 404); - assert.equal(resp.headers.get('content-type'), 'text/html; charset=utf-8'); - const text = await resp.text(); - assert.equal(text.includes('This is my static 404 page'), true); - }); - - it('does not pass through 404 request', async () => { - let testServerCalls = 0; - const testServer = createServer((_req, res) => { - testServerCalls++; - res.writeHead(200); - res.end(); - }); - testServer.listen(5678); - const entryURL = new URL( - './fixtures/redirects/.netlify/v1/functions/ssr/ssr.mjs', - import.meta.url, - ); - const { default: handler } = await import(entryURL); - const resp = await handler(new Request('http://localhost:5678/nonexistant-page'), {}); - assert.equal(resp.status, 404); - assert.equal(testServerCalls, 0); - testServer.close(); - }); - }, - { - timeout: 120000, - }, -); diff --git a/packages/integrations/netlify/test/functions/redirects.test.ts b/packages/integrations/netlify/test/functions/redirects.test.ts new file mode 100644 index 000000000000..9f983ea5cabc --- /dev/null +++ b/packages/integrations/netlify/test/functions/redirects.test.ts @@ -0,0 +1,62 @@ +import * as assert from 'node:assert/strict'; +import { createServer } from 'node:http'; +import { before, describe, it } from 'node:test'; +import { type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; + +describe('SSR - Redirects', { timeout: 120000 }, () => { + let fixture: Fixture; + + before(async () => { + fixture = await loadFixture({ root: new URL('./fixtures/redirects/', import.meta.url) }); + await fixture.build({}); + }); + + it('Creates a redirects file', async () => { + const redirects = await fixture.readFile('./_redirects'); + const parts = redirects.split(/\s+/); + // based on https://github.com/withastro/astro/issues/16030 for the default option `trailingSlash: 'ignore'` both variants should be generated + assert.deepEqual(parts, ['', '/other/', '/', '301', '/other', '/', '301', '']); + }); + + it('Does not create .html files', async () => { + let hasErrored = false; + try { + await fixture.readFile('/other/index.html'); + } catch { + hasErrored = true; + } + assert.equal(hasErrored, true, 'this file should not exist'); + }); + + it('renders static 404 page', async () => { + const entryURL = new URL( + './fixtures/redirects/.netlify/v1/functions/ssr/ssr.mjs', + import.meta.url, + ); + const { default: handler } = await import(entryURL.href); + const resp = await handler(new Request('http://example.com/nonexistant-page'), {}); + assert.equal(resp.status, 404); + assert.equal(resp.headers.get('content-type'), 'text/html; charset=utf-8'); + const text = await resp.text(); + assert.equal(text.includes('This is my static 404 page'), true); + }); + + it('does not pass through 404 request', async () => { + let testServerCalls = 0; + const testServer = createServer((_req, res) => { + testServerCalls++; + res.writeHead(200); + res.end(); + }); + testServer.listen(5678); + const entryURL = new URL( + './fixtures/redirects/.netlify/v1/functions/ssr/ssr.mjs', + import.meta.url, + ); + const { default: handler } = await import(entryURL.href); + const resp = await handler(new Request('http://localhost:5678/nonexistant-page'), {}); + assert.equal(resp.status, 404); + assert.equal(testServerCalls, 0); + testServer.close(); + }); +}); diff --git a/packages/integrations/netlify/test/functions/sessions.test.js b/packages/integrations/netlify/test/functions/sessions.test.ts similarity index 90% rename from packages/integrations/netlify/test/functions/sessions.test.js rename to packages/integrations/netlify/test/functions/sessions.test.ts index 7b41cb129648..89d4a1b8d432 100644 --- a/packages/integrations/netlify/test/functions/sessions.test.js +++ b/packages/integrations/netlify/test/functions/sessions.test.ts @@ -1,10 +1,9 @@ -// @ts-check import assert from 'node:assert/strict'; import { mkdir, rm } from 'node:fs/promises'; import { after, before, describe, it } from 'node:test'; import { BlobsServer } from '@netlify/blobs/server'; import * as devalue from 'devalue'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; +import { type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; import netlify from '../../dist/index.js'; import { sessionDrivers } from 'astro/config'; @@ -14,11 +13,9 @@ const dataDir = '.netlify/sessions'; describe('Astro.session', () => { describe('Production', () => { - /** @type {import('../../../../astro/test/test-utils.js').Fixture} */ - let fixture; + let fixture: Fixture; - /** @type {BlobsServer} */ - let blobServer; + let blobServer: BlobsServer; before(async () => { process.env.NETLIFY = '1'; await rm(dataDir, { recursive: true, force: true }).catch(() => {}); @@ -35,6 +32,7 @@ describe('Astro.session', () => { output: 'server', adapter: netlify(), session: { + // @ts-ignore - session driver types are complex generics driver: sessionDrivers.netlifyBlobs({ name: 'test', uncachedEdgeURL: `http://localhost:8971`, @@ -52,17 +50,12 @@ describe('Astro.session', () => { const mod = await import(entryURL.href); handler = mod.default; }); - /** @type {(request: Request, options: {}) => Promise} */ - let handler; + let handler: (request: Request, options: Record) => Promise; after(async () => { await blobServer.stop(); delete process.env.NETLIFY; }); - /** - * @param {string} path - * @param {RequestInit} requestInit - */ - function fetchResponse(path, requestInit) { + function fetchResponse(path: string, requestInit: RequestInit) { return handler(new Request(new URL(path, 'http://example.com'), requestInit), {}); } diff --git a/packages/integrations/netlify/test/functions/skew-protection.test.js b/packages/integrations/netlify/test/functions/skew-protection.test.js deleted file mode 100644 index ee5f8c840689..000000000000 --- a/packages/integrations/netlify/test/functions/skew-protection.test.js +++ /dev/null @@ -1,70 +0,0 @@ -import * as assert from 'node:assert/strict'; -import { readFile } from 'node:fs/promises'; -import { before, describe, it } from 'node:test'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; - -describe( - 'Skew Protection', - () => { - let fixture; - - before(async () => { - // Set DEPLOY_ID env var for the test - process.env.DEPLOY_ID = 'test-deploy-123'; - - fixture = await loadFixture({ - root: new URL('./fixtures/skew-protection/', import.meta.url), - }); - await fixture.build(); - - // Clean up - delete process.env.DEPLOY_ID; - }); - - it('Server islands inline adapter headers', async () => { - // Render a page with server islands and check the HTML contains inline headers - const entryURL = new URL( - './fixtures/skew-protection/.netlify/v1/functions/ssr/ssr.mjs', - import.meta.url, - ); - const { default: handler } = await import(entryURL); - const resp = await handler(new Request('http://example.com/server-island'), {}); - const html = await resp.text(); - - // Check that the HTML contains the inline headers in the server island script - // Should have something like: const headers = new Headers({"X-Netlify-Deploy-ID":"test-deploy-123"}); - assert.ok( - html.includes('test-deploy-123'), - 'Expected server island HTML to include deploy ID in inline script', - ); - }); - - it('Manifest contains internalFetchHeaders', async () => { - // The manifest is embedded in the build output - // Check the manifest file which contains the serialized manifest - const manifestURL = new URL( - './fixtures/skew-protection/.netlify/build/chunks/', - import.meta.url, - ); - - // Find the manifest file (it has a hash in the name) - const { readdir } = await import('node:fs/promises'); - const files = await readdir(manifestURL); - let found = false; - for (const file of files) { - const contents = await readFile(new URL(file, manifestURL), 'utf-8'); - if (contents.includes('"internalFetchHeaders":{"X-Netlify-Deploy-ID":"test-deploy-123"}')) { - found = true; - break; - } - } - assert.ok( - found, - 'Manifest should include internalFetchHeaders field with the correct deploy ID value', - ); - }); - }, - { - timeout: 120000, - }, -); diff --git a/packages/integrations/netlify/test/functions/skew-protection.test.ts b/packages/integrations/netlify/test/functions/skew-protection.test.ts new file mode 100644 index 000000000000..e2dec88abfcf --- /dev/null +++ b/packages/integrations/netlify/test/functions/skew-protection.test.ts @@ -0,0 +1,64 @@ +import * as assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import { before, describe, it } from 'node:test'; +import { type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; + +describe('Skew Protection', { timeout: 120000 }, () => { + let fixture: Fixture; + + before(async () => { + // Set DEPLOY_ID env var for the test + process.env.DEPLOY_ID = 'test-deploy-123'; + + fixture = await loadFixture({ + root: new URL('./fixtures/skew-protection/', import.meta.url), + }); + await fixture.build({}); + + // Clean up + delete process.env.DEPLOY_ID; + }); + + it('Server islands inline adapter headers', async () => { + // Render a page with server islands and check the HTML contains inline headers + const entryURL = new URL( + './fixtures/skew-protection/.netlify/v1/functions/ssr/ssr.mjs', + import.meta.url, + ); + const { default: handler } = await import(entryURL.href); + const resp = await handler(new Request('http://example.com/server-island'), {}); + const html = await resp.text(); + + // Check that the HTML contains the inline headers in the server island script + // Should have something like: const headers = new Headers({"X-Netlify-Deploy-ID":"test-deploy-123"}); + assert.ok( + html.includes('test-deploy-123'), + 'Expected server island HTML to include deploy ID in inline script', + ); + }); + + it('Manifest contains internalFetchHeaders', async () => { + // The manifest is embedded in the build output + // Check the manifest file which contains the serialized manifest + const manifestURL = new URL( + './fixtures/skew-protection/.netlify/build/chunks/', + import.meta.url, + ); + + // Find the manifest file (it has a hash in the name) + const { readdir } = await import('node:fs/promises'); + const files = await readdir(manifestURL); + let found = false; + for (const file of files) { + const contents = await readFile(new URL(file, manifestURL), 'utf-8'); + if (contents.includes('"internalFetchHeaders":{"X-Netlify-Deploy-ID":"test-deploy-123"}')) { + found = true; + break; + } + } + assert.ok( + found, + 'Manifest should include internalFetchHeaders field with the correct deploy ID value', + ); + }); +}); diff --git a/packages/integrations/netlify/test/hosted/hosted.test.js b/packages/integrations/netlify/test/hosted/hosted.test.ts similarity index 94% rename from packages/integrations/netlify/test/hosted/hosted.test.js rename to packages/integrations/netlify/test/hosted/hosted.test.ts index 3bc9349f960b..fb671afb4008 100644 --- a/packages/integrations/netlify/test/hosted/hosted.test.js +++ b/packages/integrations/netlify/test/hosted/hosted.test.ts @@ -24,6 +24,6 @@ describe('Hosted Netlify Tests', () => { const responseTwo = await fetch(`${NETLIFY_TEST_URL}/time`).then((res) => res.text()); - assert.notEqual(responseOne.body, responseTwo.body); + assert.notEqual(responseOne, responseTwo); }); }); diff --git a/packages/integrations/netlify/test/static/headers.test.js b/packages/integrations/netlify/test/static/headers.test.ts similarity index 81% rename from packages/integrations/netlify/test/static/headers.test.js rename to packages/integrations/netlify/test/static/headers.test.ts index 5c1400098b10..d777cbc824ae 100644 --- a/packages/integrations/netlify/test/static/headers.test.js +++ b/packages/integrations/netlify/test/static/headers.test.ts @@ -1,13 +1,13 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; +import { type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; describe('SSG - headers', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/redirects/', import.meta.url) }); - await fixture.build(); + await fixture.build({}); }); it('Generates headers for static assets', async () => { diff --git a/packages/integrations/netlify/test/static/image-missing-dimension.test.js b/packages/integrations/netlify/test/static/image-missing-dimension.test.ts similarity index 92% rename from packages/integrations/netlify/test/static/image-missing-dimension.test.js rename to packages/integrations/netlify/test/static/image-missing-dimension.test.ts index 932958ad399d..3519081e1160 100644 --- a/packages/integrations/netlify/test/static/image-missing-dimension.test.js +++ b/packages/integrations/netlify/test/static/image-missing-dimension.test.ts @@ -9,12 +9,12 @@ describe('Image validation when is not size specification in netlify.', () => { }); try { - await fixture.build(); + await fixture.build({}); assert.fail(); } catch (e) { // check the error image about missing image dimension assert.match( - e.name, + (e as Error).name, /MissingImageDimension/, `Build failed but not with the expected "MissingImageDimension"`, ); diff --git a/packages/integrations/netlify/test/static/redirects.test.js b/packages/integrations/netlify/test/static/redirects.test.ts similarity index 85% rename from packages/integrations/netlify/test/static/redirects.test.js rename to packages/integrations/netlify/test/static/redirects.test.ts index 9e9d0c87298e..e541efa58117 100644 --- a/packages/integrations/netlify/test/static/redirects.test.js +++ b/packages/integrations/netlify/test/static/redirects.test.ts @@ -1,13 +1,13 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; +import { type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; describe('SSG - Redirects', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/redirects/', import.meta.url) }); - await fixture.build(); + await fixture.build({}); }); it('Creates a redirects file', async () => { diff --git a/packages/integrations/netlify/test/static/static-headers.test.js b/packages/integrations/netlify/test/static/static-headers.test.ts similarity index 86% rename from packages/integrations/netlify/test/static/static-headers.test.js rename to packages/integrations/netlify/test/static/static-headers.test.ts index a5b7014894b0..5a478eccfed7 100644 --- a/packages/integrations/netlify/test/static/static-headers.test.js +++ b/packages/integrations/netlify/test/static/static-headers.test.ts @@ -1,14 +1,14 @@ import * as assert from 'node:assert/strict'; import { existsSync, readdirSync } from 'node:fs'; import { before, describe, it } from 'node:test'; -import { loadFixture } from '../../../../astro/test/test-utils.js'; +import { type Fixture, loadFixture } from '../../../../astro/test/test-utils.js'; describe('Static headers', () => { - let fixture; + let fixture: Fixture; before(async () => { fixture = await loadFixture({ root: new URL('./fixtures/static-headers/', import.meta.url) }); - await fixture.build(); + await fixture.build({}); }); it('SSR function is generated when server islands are used with output: static', async () => { @@ -23,7 +23,7 @@ describe('Static headers', () => { it('CSP headers are added when CSP is enabled', async () => { const config = await fixture.readFile('../.netlify/v1/config.json'); const headers = JSON.parse(config).headers; - const index = headers.find((x) => x.for === '/'); + const index = headers.find((x: { for: string }) => x.for === '/'); assert.notEqual(index, undefined, 'the index must have CSP headers'); assert.notEqual( diff --git a/packages/integrations/netlify/tsconfig.test.json b/packages/integrations/netlify/tsconfig.test.json new file mode 100644 index 000000000000..e8d5292cf781 --- /dev/null +++ b/packages/integrations/netlify/tsconfig.test.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["test/**/*.ts"], + "exclude": ["test/fixtures/**", "test/*/fixtures/**", "test/hosted/hosted-astro-project/**"], + "compilerOptions": { + "noEmit": true, + "allowJs": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "rewriteRelativeImportExtensions": true, + "rootDir": "." + }, + "references": [{ "path": "../../astro/tsconfig.test.json" }] +}