diff options
author | 2024-10-15 15:34:42 +0800 | |
---|---|---|
committer | 2024-10-15 15:34:42 +0800 | |
commit | 64bb796c0fee551b8b2349b5246946f60080565b (patch) | |
tree | 6caff13adaa98b6cb46901a433f332a25da4ee63 | |
parent | 5ab2d980aae8c92e71e01a8bd21c8d771e576085 (diff) | |
download | astro-64bb796c0fee551b8b2349b5246946f60080565b.tar.gz astro-64bb796c0fee551b8b2349b5246946f60080565b.tar.zst astro-64bb796c0fee551b8b2349b5246946f60080565b.zip |
Use real filesystem for unit testing (#12172)
38 files changed, 583 insertions, 945 deletions
diff --git a/.gitignore b/.gitignore index 8e6d78354..d6a28ec1b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,8 @@ package-lock.json packages/astro/src/**/*.prebuilt.ts packages/astro/src/**/*.prebuilt-dev.ts -!packages/astro/vendor/vite/dist +packages/astro/test/units/_temp-fixtures/* +!packages/astro/test/units/_temp-fixtures/package.json packages/integrations/**/.netlify/ # exclude IntelliJ/WebStorm stuff diff --git a/biome.jsonc b/biome.jsonc index a94930143..227f37a08 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -6,6 +6,7 @@ "**/dist/**", "**/smoke/**", "**/fixtures/**", + "**/_temp-fixtures/**", "**/vendor/**", "**/.vercel/**", ], diff --git a/package.json b/package.json index 7c2919886..683384edf 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,9 @@ "allowAny": [ "astro" ] + }, + "patchedDependencies": { + "fs-fixture@2.4.0": "patches/fs-fixture@2.4.0.patch" } } } diff --git a/packages/astro/package.json b/packages/astro/package.json index 09e71a830..054bc92f3 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -112,14 +112,15 @@ "build": "pnpm run prebuild && astro-scripts build \"src/**/*.{ts,js}\" --copy-wasm && tsc", "build:ci": "pnpm run prebuild && astro-scripts build \"src/**/*.{ts,js}\" --copy-wasm", "dev": "astro-scripts dev --copy-wasm --prebuild \"src/runtime/server/astro-island.ts\" --prebuild \"src/runtime/client/{idle,load,media,only,visible}.ts\" \"src/**/*.{ts,js}\"", - "test": "pnpm run test:node && pnpm run test:types", - "test:match": "pnpm run test:node --match", + "test": "pnpm run test:unit && pnpm run test:integration && pnpm run test:types", + "test:match": "astro-scripts test \"test/**/*.test.js\" --match", "test:e2e": "pnpm test:e2e:chrome && pnpm test:e2e:firefox", "test:e2e:match": "playwright test -g", "test:e2e:chrome": "playwright test", "test:e2e:firefox": "playwright test --config playwright.firefox.config.js", "test:types": "tsc --project tsconfig.tests.json", - "test:node": "astro-scripts test \"test/**/*.test.js\"" + "test:unit": "astro-scripts test \"test/units/**/*.test.js\" --teardown ./test/units/teardown.js", + "test:integration": "astro-scripts test \"test/*.test.js\"" }, "dependencies": { "@astrojs/compiler": "^2.10.3", @@ -210,9 +211,9 @@ "eol": "^0.10.0", "execa": "^8.0.1", "expect-type": "^1.1.0", + "fs-fixture": "^2.4.0", "mdast-util-mdx": "^3.0.0", "mdast-util-mdx-jsx": "^3.1.3", - "memfs": "^4.14.0", "node-mocks-http": "^1.16.1", "parse-srcset": "^1.0.2", "rehype-autolink-headings": "^7.1.0", diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index 3b75a3843..55f12216d 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -77,6 +77,9 @@ export default function createVitePluginAstroServer({ } process.on('unhandledRejection', handleUnhandledRejection); + viteServer.httpServer?.on('close', () => { + process.off('unhandledRejection', handleUnhandledRejection); + }); return () => { // Push this middleware to the front of the stack so that it can intercept responses. diff --git a/packages/astro/test/fixtures/content-mixed-errors/astro.config.mjs b/packages/astro/test/fixtures/content-mixed-errors/astro.config.mjs deleted file mode 100644 index 882e6515a..000000000 --- a/packages/astro/test/fixtures/content-mixed-errors/astro.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { defineConfig } from 'astro/config'; - -// https://astro.build/config -export default defineConfig({}); diff --git a/packages/astro/test/fixtures/content-mixed-errors/package.json b/packages/astro/test/fixtures/content-mixed-errors/package.json deleted file mode 100644 index d90bfabda..000000000 --- a/packages/astro/test/fixtures/content-mixed-errors/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@test/content-mixed-errors", - "type": "module", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "astro dev", - "start": "astro dev", - "build": "astro build", - "preview": "astro preview", - "astro": "astro" - }, - "dependencies": { - "astro": "workspace:*" - } -} diff --git a/packages/astro/test/fixtures/content-mixed-errors/src/content/authors/placeholder.json b/packages/astro/test/fixtures/content-mixed-errors/src/content/authors/placeholder.json deleted file mode 100644 index 64ae1c04c..000000000 --- a/packages/astro/test/fixtures/content-mixed-errors/src/content/authors/placeholder.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "Placeholder" -} diff --git a/packages/astro/test/fixtures/content-mixed-errors/src/content/blog/placeholder.md b/packages/astro/test/fixtures/content-mixed-errors/src/content/blog/placeholder.md deleted file mode 100644 index f7f65691b..000000000 --- a/packages/astro/test/fixtures/content-mixed-errors/src/content/blog/placeholder.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Placeholder post ---- diff --git a/packages/astro/test/fixtures/content-mixed-errors/src/pages/authors.astro b/packages/astro/test/fixtures/content-mixed-errors/src/pages/authors.astro deleted file mode 100644 index 8352a3d27..000000000 --- a/packages/astro/test/fixtures/content-mixed-errors/src/pages/authors.astro +++ /dev/null @@ -1,10 +0,0 @@ ---- -import { getCollection } from 'astro:content'; -try { - await getCollection('authors') -} catch (e) { - return e -} ---- - -<h1>Worked</h1> diff --git a/packages/astro/test/fixtures/content-mixed-errors/src/pages/blog.astro b/packages/astro/test/fixtures/content-mixed-errors/src/pages/blog.astro deleted file mode 100644 index 0d5d2836e..000000000 --- a/packages/astro/test/fixtures/content-mixed-errors/src/pages/blog.astro +++ /dev/null @@ -1,7 +0,0 @@ ---- -import { getCollection } from 'astro:content'; - -await getCollection('blog') ---- - -<h1>Worked</h1> diff --git a/packages/astro/test/units/_temp-fixtures/package.json b/packages/astro/test/units/_temp-fixtures/package.json new file mode 100644 index 000000000..3ecea0bfe --- /dev/null +++ b/packages/astro/test/units/_temp-fixtures/package.json @@ -0,0 +1,8 @@ +{ + "name": "astro-temp-fixtures", + "description": "This directory contains nested directories of dynamically created unit test fixtures. The deps here can be used by them", + "dependencies": { + "@astrojs/mdx": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/units/config/format.test.js b/packages/astro/test/units/config/format.test.js index 7b0c88d73..66938a03a 100644 --- a/packages/astro/test/units/config/format.test.js +++ b/packages/astro/test/units/config/format.test.js @@ -1,25 +1,19 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; -import { createFs, runInContainer } from '../test-utils.js'; - -const root = new URL('../../fixtures/tailwindcss-ts/', import.meta.url); +import { createFixture, runInContainer } from '../test-utils.js'; describe('Astro config formats', () => { it('An mjs config can import TypeScript modules', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ``, - '/src/stuff.ts': `export default 'works';`, - '/astro.config.mjs': ` + const fixture = await createFixture({ + '/src/pages/index.astro': ``, + '/src/stuff.ts': `export default 'works';`, + '/astro.config.mjs': `\ import stuff from './src/stuff.ts'; export default {} `, - }, - root, - ); + }); - await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, () => { + await runInContainer({ inlineConfig: { root: fixture.path } }, () => { assert.equal( true, true, diff --git a/packages/astro/test/units/content-collections/frontmatter.test.js b/packages/astro/test/units/content-collections/frontmatter.test.js index 2a3cd31ec..4f587a90f 100644 --- a/packages/astro/test/units/content-collections/frontmatter.test.js +++ b/packages/astro/test/units/content-collections/frontmatter.test.js @@ -1,33 +1,16 @@ -import nodeFS from 'node:fs'; -import path from 'node:path'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import { attachContentServerListeners } from '../../../dist/content/index.js'; -import { createFs, runInContainer, triggerFSEvent } from '../test-utils.js'; - -const root = new URL('../../fixtures/alias/', import.meta.url); - -function getTypesDts() { - const typesdtsURL = new URL('../../../templates/content/types.d.ts', import.meta.url); - const relpath = path - .relative(fileURLToPath(root), fileURLToPath(typesdtsURL)) - .replace(/\\/g, '/'); - return { - [relpath]: nodeFS.readFileSync(typesdtsURL, 'utf-8'), - }; -} +import { createFixture, runInContainer } from '../test-utils.js'; describe('frontmatter', () => { it('errors in content/ does not crash server', async () => { - const fs = createFs( - { - ...getTypesDts(), - '/src/content/posts/blog.md': ` + const fixture = await createFixture({ + '/src/content/posts/blog.md': `\ --- title: One --- `, - '/src/content/config.ts': ` + '/src/content/config.ts': `\ import { defineCollection, z } from 'astro:content'; const posts = defineCollection({ @@ -38,7 +21,7 @@ describe('frontmatter', () => { posts }; `, - '/src/pages/index.astro': ` + '/src/pages/index.astro': `\ --- --- <html> @@ -48,14 +31,12 @@ describe('frontmatter', () => { </body> </html> `, - }, - root, - ); + }); - await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, async (container) => { + await runInContainer({ inlineConfig: { root: fixture.path } }, async (container) => { await attachContentServerListeners(container); - fs.writeFileFromRootSync( + await fixture.writeFile( '/src/content/posts/blog.md', ` --- @@ -64,7 +45,6 @@ describe('frontmatter', () => { --- `, ); - triggerFSEvent(container, fs, '/src/content/posts/blog.md', 'change'); await new Promise((resolve) => setTimeout(resolve, 100)); // Note, if we got here, it didn't crash }); diff --git a/packages/astro/test/units/correct-path.js b/packages/astro/test/units/correct-path.js deleted file mode 100644 index 026baeaf6..000000000 --- a/packages/astro/test/units/correct-path.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * correctPath.js <https://github.com/streamich/fs-monkey/blob/af36a890d8070b25b9eae7178824f653bad5621f/src/correctPath.js> - * Taken from: - * https://github.com/streamich/fs-monkeys - */ - -const isWin = process.platform === 'win32'; - -/*! - * removeTrailingSeparator <https://github.com/darsain/remove-trailing-separator> - * - * Inlined from: - * Copyright (c) darsain. - * Released under the ISC License. - */ -function removeTrailingSeparator(str) { - let i = str.length - 1; - if (i < 2) { - return str; - } - while (isSeparator(str, i)) { - i--; - } - return str.substr(0, i + 1); -} - -function isSeparator(str, i) { - let char = str[i]; - return i > 0 && (char === '/' || (isWin && char === '\\')); -} - -/*! - * normalize-path <https://github.com/jonschlinkert/normalize-path> - * - * Inlined from: - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ -function normalizePath(str, stripTrailing) { - if (typeof str !== 'string') { - throw new TypeError('expected a string'); - } - str = str.replace(/[\\/]+/g, '/'); - if (stripTrailing !== false) { - str = removeTrailingSeparator(str); - } - return str; -} - -/*! - * unixify <https://github.com/jonschlinkert/unixify> - * - * Inlined from: - * Copyright (c) 2014, 2017, Jon Schlinkert. - * Released under the MIT License. - */ -export function unixify(filepath, stripTrailing = true) { - if (isWin) { - filepath = normalizePath(filepath, stripTrailing); - return filepath.replace(/^([a-zA-Z]+:|\.\/)/, ''); - } - return filepath; -} - -/* - * Corrects a windows path to unix format (including \\?\c:...) - */ -export function correctPath(filepath) { - return unixify(filepath.replace(/^\\\\\?\\.:\\/, '\\')); -} diff --git a/packages/astro/test/units/dev/base.test.js b/packages/astro/test/units/dev/base.test.js index e625df478..f230ad563 100644 --- a/packages/astro/test/units/dev/base.test.js +++ b/packages/astro/test/units/dev/base.test.js @@ -1,26 +1,19 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; -import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js'; - -const root = new URL('../../fixtures/alias/', import.meta.url); +import { createFixture, createRequestAndResponse, runInContainer } from '../test-utils.js'; describe('base configuration', () => { describe('with trailingSlash: "never"', () => { describe('index route', () => { it('Requests that include a trailing slash 404', async () => { - const fs = createFs( - { - '/src/pages/index.astro': `<h1>testing</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': `<h1>testing</h1>`, + }); await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, base: '/docs', trailingSlash: 'never', }, @@ -38,18 +31,15 @@ describe('base configuration', () => { }); it('Requests that exclude a trailing slash 200', async () => { - const fs = createFs( - { - '/src/pages/index.astro': `<h1>testing</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': `<h1>testing</h1>`, + }); await runInContainer( { fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, base: '/docs', trailingSlash: 'never', }, @@ -69,18 +59,14 @@ describe('base configuration', () => { describe('sub route', () => { it('Requests that include a trailing slash 404', async () => { - const fs = createFs( - { - '/src/pages/sub/index.astro': `<h1>testing</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/sub/index.astro': `<h1>testing</h1>`, + }); await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, base: '/docs', trailingSlash: 'never', }, @@ -98,18 +84,14 @@ describe('base configuration', () => { }); it('Requests that exclude a trailing slash 200', async () => { - const fs = createFs( - { - '/src/pages/sub/index.astro': `<h1>testing</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/sub/index.astro': `<h1>testing</h1>`, + }); await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, base: '/docs', trailingSlash: 'never', }, diff --git a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js index 3417650fc..9da0c776f 100644 --- a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js +++ b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js @@ -1,129 +1,146 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import _sync from '../../../dist/core/sync/index.js'; -import { createFsWithFallback } from '../test-utils.js'; +import { createFixture } from '../test-utils.js'; -const root = new URL('../../fixtures/content-mixed-errors/', import.meta.url); - -async function sync({ fs }) { +async function sync(root) { try { - await _sync( - { - root: fileURLToPath(root), - logLevel: 'silent', - }, - { - fs, - }, - ); + await _sync({ + root, + logLevel: 'silent', + }); return 0; - } catch (_) { + } catch { return 1; } } +const baseFileTree = { + '/src/content/authors/placeholder.json': `{ "name": "Placeholder" }`, + '/src/content/blog/placeholder.md': `\ +--- +title: Placeholder post +--- +`, + '/src/pages/authors.astro': `\ +--- +import { getCollection } from 'astro:content'; +try { + await getCollection('authors') +} catch (e) { + return e +} +--- + +<h1>Worked</h1> +`, + '/src/pages/blog.astro': `\ +--- +import { getCollection } from 'astro:content'; + +await getCollection('blog') +--- + +<h1>Worked</h1>`, +}; + describe('Content Collections - mixed content errors', () => { it('raises "mixed content" error when content in data collection', async () => { - const fs = createFsWithFallback( - { - '/src/content/authors/ben.md': `--- + const fixture = await createFixture({ + ...baseFileTree, + '/src/content/authors/ben.md': `\ +--- name: Ben --- -# Ben`, - '/src/content/authors/tony.json': `{ "name": "Tony" }`, - '/src/content/config.ts': ` - - import { z, defineCollection } from 'astro:content'; - - const authors = defineCollection({ - type: 'data', - schema: z.object({ - name: z.string(), - }), - }); +# Ben +`, + '/src/content/authors/tony.json': `{ "name": "Tony" }`, + '/src/content/config.ts': `\ +import { z, defineCollection } from 'astro:content'; + +const authors = defineCollection({ + type: 'data', + schema: z.object({ + name: z.string(), + }), +}); - export const collections = { authors };`, - }, - root, - ); +export const collections = { authors }; +`, + }); - assert.equal(await sync({ fs }), 1); + assert.equal(await sync(fixture.path), 1); }); it('raises "mixed content" error when data in content collection', async () => { - const fs = createFsWithFallback( - { - '/src/content/blog/post.md': `--- + const fixture = await createFixture({ + ...baseFileTree, + '/src/content/blog/post.md': `\ +--- title: Post --- -# Post`, - '/src/content/blog/post.yaml': `title: YAML Post`, - '/src/content/config.ts': ` - - import { z, defineCollection } from 'astro:content'; - - const blog = defineCollection({ - type: 'content', - schema: z.object({ - title: z.string(), - }), - }); +# Post +`, + '/src/content/blog/post.yaml': `title: YAML Post`, + '/src/content/config.ts': `\ +import { z, defineCollection } from 'astro:content'; + +const blog = defineCollection({ + type: 'content', + schema: z.object({ + title: z.string(), + }), +}); - export const collections = { blog };`, - }, - root, - ); +export const collections = { blog }; +`, + }); - assert.equal(await sync({ fs }), 1); + assert.equal(await sync(fixture.path), 1); }); it('raises error when data collection configured as content collection', async () => { - const fs = createFsWithFallback( - { - '/src/content/banners/welcome.json': `{ "src": "/example", "alt": "Welcome" }`, - '/src/content/config.ts': ` - - import { z, defineCollection } from 'astro:content'; - - const banners = defineCollection({ - schema: z.object({ - src: z.string(), - alt: z.string(), - }), - }); - - export const collections = { banners };`, - }, - root, - ); + const fixture = await createFixture({ + ...baseFileTree, + '/src/content/banners/welcome.json': `{ "src": "/example", "alt": "Welcome" }`, + '/src/content/config.ts': `\ +import { z, defineCollection } from 'astro:content'; + +const banners = defineCollection({ + schema: z.object({ + src: z.string(), + alt: z.string(), + }), +}); + +export const collections = { banners }; +`, + }); - assert.equal(await sync({ fs }), 1); + assert.equal(await sync(fixture.path), 1); }); it('does not raise error for empty collection with config', async () => { - const fs = createFsWithFallback( - { - // Add placeholder to ensure directory exists - '/src/content/i18n/_placeholder.txt': 'Need content here', - '/src/content/config.ts': ` - import { z, defineCollection } from 'astro:content'; - - const i18n = defineCollection({ - type: 'data', - schema: z.object({ - greeting: z.string(), - }), - }); - - export const collections = { i18n };`, - }, - root, - ); + const fixture = await createFixture({ + ...baseFileTree, + // Add placeholder to ensure directory exists + '/src/content/i18n/_placeholder.txt': 'Need content here', + '/src/content/config.ts': `\ +import { z, defineCollection } from 'astro:content'; + +const i18n = defineCollection({ + type: 'data', + schema: z.object({ + greeting: z.string(), + }), +}); + +export const collections = { i18n }; +`, + }); - const res = await sync({ fs }); - assert.equal(res, 0); + assert.equal(await sync(fixture.path), 0); }); }); diff --git a/packages/astro/test/units/dev/collections-renderentry.test.js b/packages/astro/test/units/dev/collections-renderentry.test.js index 082cd6b2f..6bd906c9c 100644 --- a/packages/astro/test/units/dev/collections-renderentry.test.js +++ b/packages/astro/test/units/dev/collections-renderentry.test.js @@ -1,12 +1,39 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import * as cheerio from 'cheerio'; import { attachContentServerListeners } from '../../../dist/content/server-listeners.js'; -import { createFsWithFallback, createRequestAndResponse, runInContainer } from '../test-utils.js'; +import { createFixture, createRequestAndResponse, runInContainer } from '../test-utils.js'; -const root = new URL('../../fixtures/content/', import.meta.url); +const baseFileTree = { + 'astro.config.mjs': `\ +import mdx from '@astrojs/mdx'; +export default { + integrations: [mdx()] +}; +`, + '/src/content/blog/promo/_launch-week-styles.css': `\ +body { + font-family: 'Comic Sans MS', sans-serif; +} +`, + '/src/content/blog/promo/launch-week.mdx': `\ +--- +title: 'Launch week!' +description: 'Join us for the exciting launch of SPACE BLOG' +publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' +tags: ['announcement'] +--- + +import './_launch-week-styles.css'; + +Join us for the space blog launch! + +- THIS THURSDAY +- Houston, TX +- Dress code: **interstellar casual** ✨ +`, +}; /** @type {typeof runInContainer} */ async function runInContainerWithContentListeners(params, callback) { @@ -18,9 +45,9 @@ async function runInContainerWithContentListeners(params, callback) { describe('Content Collections - render()', () => { it('can be called in a page component', async () => { - const fs = createFsWithFallback( - { - '/src/content/config.ts': ` + const fixture = await createFixture({ + ...baseFileTree, + '/src/content/config.ts': ` import { z, defineCollection } from 'astro:content'; const blog = defineCollection({ @@ -32,7 +59,7 @@ describe('Content Collections - render()', () => { export const collections = { blog }; `, - '/src/pages/index.astro': ` + '/src/pages/index.astro': ` --- import { getCollection } from 'astro:content'; const blog = await getCollection('blog'); @@ -47,15 +74,12 @@ describe('Content Collections - render()', () => { </body> </html> `, - }, - root, - ); + }); await runInContainerWithContentListeners( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, vite: { server: { middlewareMode: true } }, }, }, @@ -79,9 +103,9 @@ describe('Content Collections - render()', () => { }); it('can be used in a layout component', async () => { - const fs = createFsWithFallback( - { - '/src/components/Layout.astro': ` + const fixture = await createFixture({ + ...baseFileTree, + '/src/components/Layout.astro': ` --- import { getCollection } from 'astro:content'; const blog = await getCollection('blog'); @@ -99,7 +123,7 @@ describe('Content Collections - render()', () => { </html> `, - '/src/pages/index.astro': ` + '/src/pages/index.astro': ` --- import Layout from '../components/Layout.astro'; --- @@ -107,15 +131,12 @@ describe('Content Collections - render()', () => { <h1 slot="title">Index page</h2> </Layout> `, - }, - root, - ); + }); await runInContainerWithContentListeners( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, vite: { server: { middlewareMode: true } }, }, }, @@ -139,9 +160,9 @@ describe('Content Collections - render()', () => { }); it('can be used in a slot', async () => { - const fs = createFsWithFallback( - { - '/src/content/config.ts': ` + const fixture = await createFixture({ + ...baseFileTree, + '/src/content/config.ts': ` import { z, defineCollection } from 'astro:content'; const blog = defineCollection({ @@ -153,7 +174,7 @@ describe('Content Collections - render()', () => { export const collections = { blog }; `, - '/src/components/Layout.astro': ` + '/src/components/Layout.astro': ` <html> <head></head> <body> @@ -164,7 +185,7 @@ describe('Content Collections - render()', () => { </body> </html> `, - '/src/pages/index.astro': ` + '/src/pages/index.astro': ` --- import Layout from '../components/Layout.astro'; import { getCollection } from 'astro:content'; @@ -177,15 +198,12 @@ describe('Content Collections - render()', () => { <Content slot="main" /> </Layout> `, - }, - root, - ); + }); await runInContainerWithContentListeners( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, vite: { server: { middlewareMode: true } }, }, }, @@ -209,9 +227,9 @@ describe('Content Collections - render()', () => { }); it('can be called from any js/ts file', async () => { - const fs = createFsWithFallback( - { - '/src/content/config.ts': ` + const fixture = await createFixture({ + ...baseFileTree, + '/src/content/config.ts': ` import { z, defineCollection } from 'astro:content'; const blog = defineCollection({ @@ -223,7 +241,7 @@ describe('Content Collections - render()', () => { export const collections = { blog }; `, - '/src/launch-week.ts': ` + '/src/launch-week.ts': ` import { getCollection } from 'astro:content'; export let Content; @@ -234,7 +252,7 @@ describe('Content Collections - render()', () => { Content = mod.Content; `, - '/src/pages/index.astro': ` + '/src/pages/index.astro': ` --- import { Content } from '../launch-week.ts'; --- @@ -246,15 +264,12 @@ describe('Content Collections - render()', () => { </body> </html> `, - }, - root, - ); + }); await runInContainerWithContentListeners( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, vite: { server: { middlewareMode: true } }, }, }, diff --git a/packages/astro/test/units/dev/dev.test.js b/packages/astro/test/units/dev/dev.test.js index c82232768..74f1d1e92 100644 --- a/packages/astro/test/units/dev/dev.test.js +++ b/packages/astro/test/units/dev/dev.test.js @@ -1,21 +1,12 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import * as cheerio from 'cheerio'; -import { - createFs, - createRequestAndResponse, - runInContainer, - triggerFSEvent, -} from '../test-utils.js'; - -const root = new URL('../../fixtures/alias/', import.meta.url); +import { createFixture, createRequestAndResponse, runInContainer } from '../test-utils.js'; describe('dev container', () => { it('can render requests', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ` + const fixture = await createFixture({ + '/src/pages/index.astro': ` --- const name = 'Testing'; --- @@ -26,11 +17,9 @@ describe('dev container', () => { </body> </html> `, - }, - root, - ); + }); - await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, async (container) => { + await runInContainer({ inlineConfig: { root: fixture.path } }, async (container) => { const { req, res, text } = createRequestAndResponse({ method: 'GET', url: '/', @@ -43,89 +32,16 @@ describe('dev container', () => { }); }); - it('HMR only short circuits on previously cached modules', async () => { - const fs = createFs( - { - '/src/components/Header.astro': ` - <h1>{Astro.props.title}</h1> - `, - '/src/pages/index.astro': ` - --- - import Header from '../components/Header.astro'; - const name = 'Testing'; - --- - <html> - <head><title>{name}</title></head> - <body class="one"> - <Header title={name} /> - </body> - </html> - `, - }, - root, - ); - - await runInContainer({ fs, inlineConfig: { root: fileURLToPath(root) } }, async (container) => { - let r = createRequestAndResponse({ - method: 'GET', - url: '/', - }); - container.handle(r.req, r.res); - let html = await r.text(); - let $ = cheerio.load(html); - assert.equal($('body.one').length, 1); - - fs.writeFileFromRootSync( - '/src/components/Header.astro', - ` - <h1>{Astro.props.title}</h1> - `, - ); - triggerFSEvent(container, fs, '/src/components/Header.astro', 'change'); - - fs.writeFileFromRootSync( - '/src/pages/index.astro', - ` - --- - import Header from '../components/Header.astro'; - const name = 'Testing'; - --- - <html> - <head><title>{name}</title></head> - <body class="two"> - <Header title={name} /> - </body> - </html> - `, - ); - triggerFSEvent(container, fs, '/src/pages/index.astro', 'change'); - - r = createRequestAndResponse({ - method: 'GET', - url: '/', - }); - container.handle(r.req, r.res); - html = await r.text(); - $ = cheerio.load(html); - assert.equal($('body.one').length, 0); - assert.equal($('body.two').length, 1); - }); - }); - it('Allows dynamic segments in injected routes', async () => { - const fs = createFs( - { - '/src/components/test.astro': `<h1>{Astro.params.slug}</h1>`, - '/src/pages/test-[slug].astro': `<h1>{Astro.params.slug}</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/components/test.astro': `<h1>{Astro.params.slug}</h1>`, + '/src/pages/test-[slug].astro': `<h1>{Astro.params.slug}</h1>`, + }); await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, output: 'server', integrations: [ { @@ -164,19 +80,15 @@ describe('dev container', () => { }); it('Serves injected 404 route for any 404', async () => { - const fs = createFs( - { - '/src/components/404.astro': `<h1>Custom 404</h1>`, - '/src/pages/page.astro': `<h1>Regular page</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/components/404.astro': `<h1>Custom 404</h1>`, + '/src/pages/page.astro': `<h1>Regular page</h1>`, + }); await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, output: 'server', integrations: [ { @@ -226,10 +138,14 @@ describe('dev container', () => { }); it('items in public/ are not available from root when using a base', async () => { + const fixture = await createFixture({ + '/public/test.txt': `Test`, + }); + await runInContainer( { inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, base: '/sub/', }, }, @@ -260,7 +176,11 @@ describe('dev container', () => { }); it('items in public/ are available from root when not using a base', async () => { - await runInContainer({ inlineConfig: { root: fileURLToPath(root) } }, async (container) => { + const fixture = await createFixture({ + '/public/test.txt': `Test`, + }); + + await runInContainer({ inlineConfig: { root: fixture.path } }, async (container) => { // Try the root path let r = createRequestAndResponse({ method: 'GET', diff --git a/packages/astro/test/units/dev/head-injection.test.js b/packages/astro/test/units/dev/head-injection.test.js index 0796ba45f..fa61cea58 100644 --- a/packages/astro/test/units/dev/head-injection.test.js +++ b/packages/astro/test/units/dev/head-injection.test.js @@ -1,14 +1,13 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import * as cheerio from 'cheerio'; -import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js'; +import { createFixture, createRequestAndResponse, runInContainer } from '../test-utils.js'; const root = new URL('../../fixtures/alias/', import.meta.url); describe('head injection', () => { it('Dynamic injection from component created in the page frontmatter', async () => { - const fs = createFs( + const fixture = await createFixture( { '/src/components/Other.astro': ` <style> @@ -64,9 +63,8 @@ describe('head injection', () => { await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, vite: { server: { middlewareMode: true } }, }, }, @@ -87,7 +85,7 @@ describe('head injection', () => { }); it('Dynamic injection from a layout component', async () => { - const fs = createFs( + const fixture = await createFixture( { '/src/components/Other.astro': ` <style> @@ -164,9 +162,8 @@ describe('head injection', () => { await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, vite: { server: { middlewareMode: true } }, }, }, diff --git a/packages/astro/test/units/dev/hydration.test.js b/packages/astro/test/units/dev/hydration.test.js index 369630f9a..03962a416 100644 --- a/packages/astro/test/units/dev/hydration.test.js +++ b/packages/astro/test/units/dev/hydration.test.js @@ -1,18 +1,14 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; -import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js'; - -const root = new URL('../../fixtures/alias/', import.meta.url); +import { createFixture, createRequestAndResponse, runInContainer } from '../test-utils.js'; describe('hydration', () => { it( 'should not crash when reassigning a hydrated component', { skip: true, todo: "It seems that `components/Client.svelte` isn't found" }, async () => { - const fs = createFs( - { - '/src/pages/index.astro': ` + const fixture = await createFixture({ + '/src/pages/index.astro': ` --- import Svelte from '../components/Client.svelte'; const Foo = Svelte; @@ -26,15 +22,12 @@ describe('hydration', () => { </body> </html> `, - }, - root, - ); + }); await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, logLevel: 'silent', }, }, diff --git a/packages/astro/test/units/dev/restart.test.js b/packages/astro/test/units/dev/restart.test.js index 339b95fc1..9d5664cbf 100644 --- a/packages/astro/test/units/dev/restart.test.js +++ b/packages/astro/test/units/dev/restart.test.js @@ -1,25 +1,27 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import * as cheerio from 'cheerio'; import { createContainerWithAutomaticRestart, startContainer, } from '../../../dist/core/dev/index.js'; -import { createFs, createRequestAndResponse, triggerFSEvent } from '../test-utils.js'; +import { createFixture, createRequestAndResponse } from '../test-utils.js'; -const root = new URL('../../fixtures/alias/', import.meta.url); +/** @type {import('astro').AstroInlineConfig} */ +const defaultInlineConfig = { + logLevel: 'silent', +}; function isStarted(container) { return !!container.viteServer.httpServer?.listening; } -describe('dev container restarts', () => { +// Checking for restarts may hang if no restarts happen, so set a 20s timeout for each test +describe('dev container restarts', { timeout: 20000 }, () => { it('Surfaces config errors on restarts', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ` + const fixture = await createFixture({ + '/src/pages/index.astro': ` <html> <head><title>Test</title></head> <body> @@ -27,16 +29,14 @@ describe('dev container restarts', () => { </body> </html> `, - '/astro.config.mjs': ` - - `, - }, - root, - ); + '/astro.config.mjs': ``, + }); const restart = await createContainerWithAutomaticRestart({ - fs, - inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' }, + inlineConfig: { + ...defaultInlineConfig, + root: fixture.path, + }, }); try { @@ -52,40 +52,37 @@ describe('dev container restarts', () => { // Create an error let restartComplete = restart.restarted(); - fs.writeFileFromRootSync('/astro.config.mjs', 'const foo = bar'); - - // Vite watches the real filesystem, so we have to mock this part. It's not so bad. + await fixture.writeFile('/astro.config.mjs', 'const foo = bar'); + // TODO: fix this hack restart.container.viteServer.watcher.emit( 'change', - fs.getFullyResolvedPath('/astro.config.mjs'), + fixture.getPath('/astro.config.mjs').replace(/\\/g, '/'), ); // Wait for the restart to finish let hmrError = await restartComplete; - assert.notEqual(typeof hmrError, 'undefined'); + assert.ok(hmrError instanceof Error); // Do it a second time to make sure we are still watching restartComplete = restart.restarted(); - fs.writeFileFromRootSync('/astro.config.mjs', 'const foo = bar2'); - - // Vite watches the real filesystem, so we have to mock this part. It's not so bad. + await fixture.writeFile('/astro.config.mjs', 'const foo = bar2'); + // TODO: fix this hack restart.container.viteServer.watcher.emit( 'change', - fs.getFullyResolvedPath('/astro.config.mjs'), + fixture.getPath('/astro.config.mjs').replace(/\\/g, '/'), ); hmrError = await restartComplete; - assert.notEqual(typeof hmrError, 'undefined'); + assert.ok(hmrError instanceof Error); } finally { await restart.container.close(); } }); it('Restarts the container if previously started', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ` + const fixture = await createFixture({ + '/src/pages/index.astro': ` <html> <head><title>Test</title></head> <body> @@ -93,14 +90,14 @@ describe('dev container restarts', () => { </body> </html> `, - '/astro.config.mjs': ``, - }, - root, - ); + '/astro.config.mjs': ``, + }); const restart = await createContainerWithAutomaticRestart({ - fs, - inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' }, + inlineConfig: { + ...defaultInlineConfig, + root: fixture.path, + }, }); await startContainer(restart.container); assert.equal(isStarted(restart.container), true); @@ -108,7 +105,12 @@ describe('dev container restarts', () => { try { // Trigger a change let restartComplete = restart.restarted(); - triggerFSEvent(restart.container, fs, '/astro.config.mjs', 'change'); + await fixture.writeFile('/astro.config.mjs', ''); + // TODO: fix this hack + restart.container.viteServer.watcher.emit( + 'change', + fixture.getPath('/astro.config.mjs').replace(/\\/g, '/'), + ); await restartComplete; assert.equal(isStarted(restart.container), true); @@ -118,18 +120,16 @@ describe('dev container restarts', () => { }); it('Is able to restart project using Tailwind + astro.config.ts', async () => { - const troot = new URL('../../fixtures/tailwindcss-ts/', import.meta.url); - const fs = createFs( - { - '/src/pages/index.astro': ``, - '/astro.config.ts': ``, - }, - troot, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': ``, + '/astro.config.ts': ``, + }); const restart = await createContainerWithAutomaticRestart({ - fs, - inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' }, + inlineConfig: { + ...defaultInlineConfig, + root: fixture.path, + }, }); await startContainer(restart.container); assert.equal(isStarted(restart.container), true); @@ -137,7 +137,12 @@ describe('dev container restarts', () => { try { // Trigger a change let restartComplete = restart.restarted(); - triggerFSEvent(restart.container, fs, '/astro.config.ts', 'change'); + await fixture.writeFile('/astro.config.ts', ''); + // TODO: fix this hack + restart.container.viteServer.watcher.emit( + 'change', + fixture.getPath('/astro.config.mjs').replace(/\\/g, '/'), + ); await restartComplete; assert.equal(isStarted(restart.container), true); @@ -147,24 +152,27 @@ describe('dev container restarts', () => { }); it('Is able to restart project on package.json changes', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ``, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': ``, + }); const restart = await createContainerWithAutomaticRestart({ - fs, - inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' }, + inlineConfig: { + ...defaultInlineConfig, + root: fixture.path, + }, }); await startContainer(restart.container); assert.equal(isStarted(restart.container), true); try { let restartComplete = restart.restarted(); - fs.writeFileSync('/package.json', `{}`); - triggerFSEvent(restart.container, fs, '/package.json', 'change'); + await fixture.writeFile('/package.json', `{}`); + // TODO: fix this hack + restart.container.viteServer.watcher.emit( + 'change', + fixture.getPath('/package.json').replace(/\\/g, '/'), + ); await restartComplete; } finally { await restart.container.close(); @@ -172,16 +180,15 @@ describe('dev container restarts', () => { }); it('Is able to restart on viteServer.restart API call', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ``, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': ``, + }); const restart = await createContainerWithAutomaticRestart({ - fs, - inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' }, + inlineConfig: { + ...defaultInlineConfig, + root: fixture.path, + }, }); await startContainer(restart.container); assert.equal(isStarted(restart.container), true); @@ -196,26 +203,28 @@ describe('dev container restarts', () => { }); it('Is able to restart project on .astro/settings.json changes', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ``, - '/.astro/settings.json': `{}`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': ``, + '/.astro/settings.json': `{}`, + }); const restart = await createContainerWithAutomaticRestart({ - fs, - inlineConfig: { root: fileURLToPath(root), logLevel: 'silent' }, + inlineConfig: { + ...defaultInlineConfig, + root: fixture.path, + }, }); await startContainer(restart.container); assert.equal(isStarted(restart.container), true); try { let restartComplete = restart.restarted(); - fs.mkdirSync('/.astro/', { recursive: true }); - fs.writeFileSync('/.astro/settings.json', `{ }`); - triggerFSEvent(restart.container, fs, '/.astro/settings.json', 'change'); + await fixture.writeFile('/.astro/settings.json', `{ }`); + // TODO: fix this hack + restart.container.viteServer.watcher.emit( + 'change', + fixture.getPath('/.astro/settings.json').replace(/\\/g, '/'), + ); await restartComplete; } finally { await restart.container.close(); diff --git a/packages/astro/test/units/render/chunk.test.js b/packages/astro/test/units/render/chunk.test.js index b9c1c70f3..57ab74326 100644 --- a/packages/astro/test/units/render/chunk.test.js +++ b/packages/astro/test/units/render/chunk.test.js @@ -1,30 +1,23 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import * as cheerio from 'cheerio'; -import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js'; - -const root = new URL('../../fixtures/alias/', import.meta.url); +import { createFixture, createRequestAndResponse, runInContainer } from '../test-utils.js'; describe('core/render chunk', () => { it('does not throw on user object with type', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ` + const fixture = await createFixture({ + '/src/pages/index.astro': `\ --- const value = { type: 'foobar' } --- <div id="chunk">{value}</div> `, - }, - root, - ); + }); await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, logLevel: 'silent', integrations: [], }, diff --git a/packages/astro/test/units/render/components.test.js b/packages/astro/test/units/render/components.test.js index b13d1fe88..991f0f105 100644 --- a/packages/astro/test/units/render/components.test.js +++ b/packages/astro/test/units/render/components.test.js @@ -1,16 +1,12 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import * as cheerio from 'cheerio'; -import { createFs, createRequestAndResponse, runInContainer } from '../test-utils.js'; - -const root = new URL('../../fixtures/alias/', import.meta.url); +import { createFixture, createRequestAndResponse, runInContainer } from '../test-utils.js'; describe('core/render components', () => { it('should sanitize dynamic tags', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ` + const fixture = await createFixture({ + '/src/pages/index.astro': ` --- const TagA = 'p style=color:red;' const TagB = 'p><script id="pwnd">console.log("pwnd")</script>' @@ -23,15 +19,12 @@ describe('core/render components', () => { </body> </html> `, - }, - root, - ); + }); await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, logLevel: 'silent', integrations: [], }, @@ -58,9 +51,8 @@ describe('core/render components', () => { }); it('should merge `class` and `class:list`', async () => { - const fs = createFs( - { - '/src/pages/index.astro': ` + const fixture = await createFixture({ + '/src/pages/index.astro': ` --- import Class from '../components/Class.astro'; import ClassList from '../components/ClassList.astro'; @@ -74,20 +66,17 @@ describe('core/render components', () => { <BothFlipped class:list={{ blue: true }} class="red" /> <BothSpread class:list={{ blue: true }} { ...{ class: "red" }} /> `, - '/src/components/Class.astro': `<pre id="class" set:html={JSON.stringify(Astro.props)} />`, - '/src/components/ClassList.astro': `<pre id="class-list" set:html={JSON.stringify(Astro.props)} />`, - '/src/components/BothLiteral.astro': `<pre id="both-literal" set:html={JSON.stringify(Astro.props)} />`, - '/src/components/BothFlipped.astro': `<pre id="both-flipped" set:html={JSON.stringify(Astro.props)} />`, - '/src/components/BothSpread.astro': `<pre id="both-spread" set:html={JSON.stringify(Astro.props)} />`, - }, - root, - ); + '/src/components/Class.astro': `<pre id="class" set:html={JSON.stringify(Astro.props)} />`, + '/src/components/ClassList.astro': `<pre id="class-list" set:html={JSON.stringify(Astro.props)} />`, + '/src/components/BothLiteral.astro': `<pre id="both-literal" set:html={JSON.stringify(Astro.props)} />`, + '/src/components/BothFlipped.astro': `<pre id="both-flipped" set:html={JSON.stringify(Astro.props)} />`, + '/src/components/BothSpread.astro': `<pre id="both-spread" set:html={JSON.stringify(Astro.props)} />`, + }); await runInContainer( { - fs, inlineConfig: { - root: fileURLToPath(root), + root: fixture.path, logLevel: 'silent', integrations: [], }, diff --git a/packages/astro/test/units/routing/endpoints.test.js b/packages/astro/test/units/routing/endpoints.test.js index 43d76af5a..8ce8cf1f5 100644 --- a/packages/astro/test/units/routing/endpoints.test.js +++ b/packages/astro/test/units/routing/endpoints.test.js @@ -1,16 +1,14 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import { createContainer } from '../../../dist/core/dev/container.js'; import testAdapter from '../../test-adapter.js'; import { createBasicSettings, - createFs, + createFixture, createRequestAndResponse, defaultLogger, } from '../test-utils.js'; -const root = new URL('../../fixtures/api-routes/', import.meta.url); const fileSystem = { '/src/pages/response-redirect.ts': `export const GET = ({ url }) => Response.redirect("https://example.com/destination", 307)`, '/src/pages/response.ts': `export const GET = ({ url }) => new Response(null, { headers: { Location: "https://example.com/destination" }, status: 307 })`, @@ -23,9 +21,9 @@ describe('endpoints', () => { let settings; before(async () => { - const fs = createFs(fileSystem, root); + const fixture = await createFixture(fileSystem); settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', adapter: testAdapter(), }); diff --git a/packages/astro/test/units/routing/manifest.test.js b/packages/astro/test/units/routing/manifest.test.js index 523d86e6a..e95137728 100644 --- a/packages/astro/test/units/routing/manifest.test.js +++ b/packages/astro/test/units/routing/manifest.test.js @@ -1,11 +1,8 @@ import * as assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import { Logger } from '../../../dist/core/logger/core.js'; import { createRouteManifest } from '../../../dist/core/routing/manifest/create.js'; -import { createBasicSettings, createFs } from '../test-utils.js'; - -const root = new URL('../../fixtures/alias/', import.meta.url); +import { createBasicSettings, createFixture } from '../test-utils.js'; function getManifestRoutes(manifest) { return manifest.routes.map((route) => ({ @@ -41,21 +38,17 @@ function assertRouteRelations(routes, relations) { describe('routing - createRouteManifest', () => { it('using trailingSlash: "never" does not match the index route when it contains a trailing slash', async () => { - const fs = createFs( - { - '/src/pages/index.astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, base: '/search', trailingSlash: 'never', }); const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); const [{ pattern }] = manifest.routes; assert.equal(pattern.test(''), true); @@ -63,15 +56,12 @@ describe('routing - createRouteManifest', () => { }); it('endpoint routes are sorted before page routes', async () => { - const fs = createFs( - { - '/src/pages/[contact].astro': `<h1>test</h1>`, - '/src/pages/[contact].ts': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/[contact].astro': `<h1>test</h1>`, + '/src/pages/[contact].ts': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, base: '/search', trailingSlash: 'never', experimental: { @@ -91,9 +81,8 @@ describe('routing - createRouteManifest', () => { ]; const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); assert.deepEqual(getManifestRoutes(manifest), [ @@ -117,18 +106,15 @@ describe('routing - createRouteManifest', () => { }); it('static routes are sorted before dynamic and rest routes', async () => { - const fs = createFs( - { - '/src/pages/[dynamic].astro': `<h1>test</h1>`, - '/src/pages/[...rest].astro': `<h1>test</h1>`, - '/src/pages/static.astro': `<h1>test</h1>`, - '/src/pages/static-[dynamic].astro': `<h1>test</h1>`, - '/src/pages/index.astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/[dynamic].astro': `<h1>test</h1>`, + '/src/pages/[...rest].astro': `<h1>test</h1>`, + '/src/pages/static.astro': `<h1>test</h1>`, + '/src/pages/static-[dynamic].astro': `<h1>test</h1>`, + '/src/pages/index.astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, base: '/search', trailingSlash: 'never', experimental: { @@ -137,9 +123,8 @@ describe('routing - createRouteManifest', () => { }); const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); assertRouteRelations(getManifestRoutes(manifest), [ @@ -154,21 +139,18 @@ describe('routing - createRouteManifest', () => { it('route sorting with multi-layer index page conflict', async () => { // Reproducing regression from https://github.com/withastro/astro/issues/10071 - const fs = createFs( - { - '/src/pages/a/1.astro': `<h1>test</h1>`, - '/src/pages/a/2.astro': `<h1>test</h1>`, - '/src/pages/a/3.astro': `<h1>test</h1>`, - '/src/pages/modules/[...slug].astro': `<h1>test</h1>`, - '/src/pages/modules/index.astro': `<h1>test</h1>`, - '/src/pages/test/[...slug].astro': `<h1>test</h1>`, - '/src/pages/test/index.astro': `<h1>test</h1>`, - '/src/pages/index.astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/a/1.astro': `<h1>test</h1>`, + '/src/pages/a/2.astro': `<h1>test</h1>`, + '/src/pages/a/3.astro': `<h1>test</h1>`, + '/src/pages/modules/[...slug].astro': `<h1>test</h1>`, + '/src/pages/modules/index.astro': `<h1>test</h1>`, + '/src/pages/test/[...slug].astro': `<h1>test</h1>`, + '/src/pages/test/index.astro': `<h1>test</h1>`, + '/src/pages/index.astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, base: '/search', trailingSlash: 'never', experimental: { @@ -177,9 +159,8 @@ describe('routing - createRouteManifest', () => { }); const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); assertRouteRelations(getManifestRoutes(manifest), [ @@ -200,24 +181,21 @@ describe('routing - createRouteManifest', () => { }); it('route sorting respects the file tree', async () => { - const fs = createFs( - { - '/src/pages/[dynamic_folder]/static.astro': `<h1>test</h1>`, - '/src/pages/[dynamic_folder]/index.astro': `<h1>test</h1>`, - '/src/pages/[dynamic_folder]/[...rest].astro': `<h1>test</h1>`, - '/src/pages/[...rest]/static.astro': `<h1>test</h1>`, - '/src/pages/[...rest]/index.astro': `<h1>test</h1>`, - '/src/pages/blog/index.astro': `<h1>test</h1>`, - '/src/pages/blog/[...slug].astro': `<h1>test</h1>`, - '/src/pages/[dynamic_file].astro': `<h1>test</h1>`, - '/src/pages/[...other].astro': `<h1>test</h1>`, - '/src/pages/static.astro': `<h1>test</h1>`, - '/src/pages/index.astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/[dynamic_folder]/static.astro': `<h1>test</h1>`, + '/src/pages/[dynamic_folder]/index.astro': `<h1>test</h1>`, + '/src/pages/[dynamic_folder]/[...rest].astro': `<h1>test</h1>`, + '/src/pages/[...rest]/static.astro': `<h1>test</h1>`, + '/src/pages/[...rest]/index.astro': `<h1>test</h1>`, + '/src/pages/blog/index.astro': `<h1>test</h1>`, + '/src/pages/blog/[...slug].astro': `<h1>test</h1>`, + '/src/pages/[dynamic_file].astro': `<h1>test</h1>`, + '/src/pages/[...other].astro': `<h1>test</h1>`, + '/src/pages/static.astro': `<h1>test</h1>`, + '/src/pages/index.astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, base: '/search', trailingSlash: 'never', experimental: { @@ -226,9 +204,8 @@ describe('routing - createRouteManifest', () => { }); const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); assertRouteRelations(getManifestRoutes(manifest), [ @@ -263,15 +240,12 @@ describe('routing - createRouteManifest', () => { }); it('injected routes are sorted in legacy mode above filesystem routes', async () => { - const fs = createFs( - { - '/src/pages/index.astro': `<h1>test</h1>`, - '/src/pages/blog/[...slug].astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': `<h1>test</h1>`, + '/src/pages/blog/[...slug].astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', base: '/search', trailingSlash: 'never', @@ -289,9 +263,8 @@ describe('routing - createRouteManifest', () => { ]; const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); assert.deepEqual(getManifestRoutes(manifest), [ @@ -315,15 +288,12 @@ describe('routing - createRouteManifest', () => { }); it('injected routes are sorted alongside filesystem routes', async () => { - const fs = createFs( - { - '/src/pages/index.astro': `<h1>test</h1>`, - '/src/pages/blog/[...slug].astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': `<h1>test</h1>`, + '/src/pages/blog/[...slug].astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', base: '/search', trailingSlash: 'never', @@ -345,9 +315,8 @@ describe('routing - createRouteManifest', () => { ]; const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); assert.deepEqual(getManifestRoutes(manifest), [ @@ -371,15 +340,12 @@ describe('routing - createRouteManifest', () => { }); it('redirects are sorted in legacy mode below the filesystem routes', async () => { - const fs = createFs( - { - '/src/pages/index.astro': `<h1>test</h1>`, - '/src/pages/blog/contributing.astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': `<h1>test</h1>`, + '/src/pages/blog/contributing.astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', base: '/search', trailingSlash: 'never', @@ -392,9 +358,8 @@ describe('routing - createRouteManifest', () => { }, }); const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); assert.deepEqual(getManifestRoutes(manifest), [ @@ -418,15 +383,12 @@ describe('routing - createRouteManifest', () => { }); it('redirects are sorted alongside the filesystem routes', async () => { - const fs = createFs( - { - '/src/pages/index.astro': `<h1>test</h1>`, - '/src/pages/blog/contributing.astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/index.astro': `<h1>test</h1>`, + '/src/pages/blog/contributing.astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', base: '/search', trailingSlash: 'never', @@ -445,9 +407,8 @@ describe('routing - createRouteManifest', () => { }, }); const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); assert.deepEqual(getManifestRoutes(manifest), [ @@ -471,14 +432,11 @@ describe('routing - createRouteManifest', () => { }); it('report colliding static routes', async () => { - const fs = createFs( - { - '/src/pages/contributing.astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/contributing.astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', base: '/search', trailingSlash: 'never', @@ -496,9 +454,8 @@ describe('routing - createRouteManifest', () => { ]; const manifestOptions = { - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }; const { logger, logs } = getLogger(); @@ -523,15 +480,12 @@ describe('routing - createRouteManifest', () => { }); it('report colliding SSR dynamic routes', async () => { - const fs = createFs( - { - '/src/pages/[foo].astro': `<h1>test</h1>`, - '/src/pages/[bar].astro': `<h1>test</h1>`, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/[foo].astro': `<h1>test</h1>`, + '/src/pages/[bar].astro': `<h1>test</h1>`, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', base: '/search', trailingSlash: 'never', @@ -542,9 +496,8 @@ describe('routing - createRouteManifest', () => { }); const manifestOptions = { - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }; const { logger, logs } = getLogger(); @@ -569,16 +522,13 @@ describe('routing - createRouteManifest', () => { }); it('should concatenate each part of the segment. issues#10122', async () => { - const fs = createFs( - { - '/src/pages/a-[b].astro': `<h1>test</h1>`, - '/src/pages/blog/a-[b].233.ts': ``, - }, - root, - ); + const fixture = await createFixture({ + '/src/pages/a-[b].astro': `<h1>test</h1>`, + '/src/pages/blog/a-[b].233.ts': ``, + }); const settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', base: '/search', trailingSlash: 'never', @@ -599,9 +549,8 @@ describe('routing - createRouteManifest', () => { ]; const manifest = createRouteManifest({ - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }); assert.deepEqual(getManifestRoutes(manifest), [ diff --git a/packages/astro/test/units/routing/route-matching.test.js b/packages/astro/test/units/routing/route-matching.test.js index a9a0e48a3..1ef383d55 100644 --- a/packages/astro/test/units/routing/route-matching.test.js +++ b/packages/astro/test/units/routing/route-matching.test.js @@ -11,12 +11,11 @@ import { createDevelopmentManifest } from '../../../dist/vite-plugin-astro-serve import testAdapter from '../../test-adapter.js'; import { createBasicSettings, - createFs, + createFixture, createRequestAndResponse, defaultLogger, } from '../test-utils.js'; -const root = new URL('../../fixtures/alias/', import.meta.url); const fileSystem = { '/src/pages/[serverDynamic].astro': ` --- @@ -131,15 +130,14 @@ describe('Route matching', () => { let settings; before(async () => { - const fs = createFs(fileSystem, root); + const fixture = await createFixture(fileSystem); settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, trailingSlash: 'never', output: 'hybrid', adapter: testAdapter(), }); container = await createContainer({ - fs, settings, logger: defaultLogger, }); @@ -149,9 +147,8 @@ describe('Route matching', () => { pipeline = DevPipeline.create(undefined, { loader, logger: defaultLogger, manifest, settings }); manifestData = createRouteManifest( { - cwd: fileURLToPath(root), + cwd: fixture.path, settings, - fsMod: fs, }, defaultLogger, ); diff --git a/packages/astro/test/units/routing/route-sanitization.test.js b/packages/astro/test/units/routing/route-sanitization.test.js index 802418868..7b6dcedf6 100644 --- a/packages/astro/test/units/routing/route-sanitization.test.js +++ b/packages/astro/test/units/routing/route-sanitization.test.js @@ -1,17 +1,15 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import * as cheerio from 'cheerio'; import { createContainer } from '../../../dist/core/dev/container.js'; import testAdapter from '../../test-adapter.js'; import { createBasicSettings, - createFs, + createFixture, createRequestAndResponse, defaultLogger, } from '../test-utils.js'; -const root = new URL('../../fixtures/alias/', import.meta.url); const fileSystem = { '/src/pages/[...testSlashTrim].astro': ` --- @@ -34,15 +32,14 @@ describe('Route sanitization', () => { let settings; before(async () => { - const fs = createFs(fileSystem, root); + const fixture = await createFixture(fileSystem); settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, trailingSlash: 'never', output: 'hybrid', adapter: testAdapter(), }); container = await createContainer({ - fs, settings, logger: defaultLogger, }); diff --git a/packages/astro/test/units/routing/trailing-slash.test.js b/packages/astro/test/units/routing/trailing-slash.test.js index a9e8fe945..d9ba53b38 100644 --- a/packages/astro/test/units/routing/trailing-slash.test.js +++ b/packages/astro/test/units/routing/trailing-slash.test.js @@ -1,16 +1,14 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import { createContainer } from '../../../dist/core/dev/container.js'; import testAdapter from '../../test-adapter.js'; import { createBasicSettings, - createFs, + createFixture, createRequestAndResponse, defaultLogger, } from '../test-utils.js'; -const root = new URL('../../fixtures/api-routes/', import.meta.url); const fileSystem = { '/src/pages/api.ts': `export const GET = () => Response.json({ success: true })`, }; @@ -20,15 +18,14 @@ describe('trailingSlash', () => { let settings; before(async () => { - const fs = createFs(fileSystem, root); + const fixture = await createFixture(fileSystem); settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, trailingSlash: 'always', output: 'server', adapter: testAdapter(), }); container = await createContainer({ - fs, settings, logger: defaultLogger, }); diff --git a/packages/astro/test/units/runtime/endpoints.test.js b/packages/astro/test/units/runtime/endpoints.test.js index 47f52a2df..313453c63 100644 --- a/packages/astro/test/units/runtime/endpoints.test.js +++ b/packages/astro/test/units/runtime/endpoints.test.js @@ -1,11 +1,10 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import { createContainer } from '../../../dist/core/dev/container.js'; import testAdapter from '../../test-adapter.js'; import { createBasicSettings, - createFs, + createFixture, createRequestAndResponse, defaultLogger, } from '../test-utils.js'; @@ -20,14 +19,13 @@ describe('endpoints', () => { let settings; before(async () => { - const fs = createFs(fileSystem, root); + const fixture = await createFixture(fileSystem, root); settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', adapter: testAdapter(), }); container = await createContainer({ - fs, settings, logger: defaultLogger, }); diff --git a/packages/astro/test/units/teardown.js b/packages/astro/test/units/teardown.js new file mode 100644 index 000000000..eeb718a37 --- /dev/null +++ b/packages/astro/test/units/teardown.js @@ -0,0 +1,19 @@ +import fs from 'node:fs'; + +export default function teardown(testPassed) { + // Delete all directories within `_temp-fixtures` directory if all test passed + if (testPassed) { + try { + const tempFixturesDir = new URL('./_temp-fixtures/', import.meta.url); + const entries = fs.readdirSync(tempFixturesDir); + for (const entry of entries) { + if (entry === 'package.json' || entry === 'node_modules') continue; + const dir = new URL(entry, tempFixturesDir); + fs.rmSync(dir, { recursive: true }); + } + } catch (e) { + console.error('Failed to delete temp fixtures'); + throw e; + } + } +} diff --git a/packages/astro/test/units/test-utils.js b/packages/astro/test/units/test-utils.js index fea8e967e..9eb635cc9 100644 --- a/packages/astro/test/units/test-utils.js +++ b/packages/astro/test/units/test-utils.js @@ -1,8 +1,7 @@ import { EventEmitter } from 'node:events'; import realFS from 'node:fs'; -import npath from 'node:path'; import { fileURLToPath } from 'node:url'; -import { Volume } from 'memfs'; +import { createFixture as _createFixture } from 'fs-fixture'; import httpMocks from 'node-mocks-http'; import { getDefaultClientDirectives } from '../../dist/core/client-directive/index.js'; import { resolveConfig } from '../../dist/core/config/index.js'; @@ -13,7 +12,6 @@ import { nodeLogDestination } from '../../dist/core/logger/node.js'; import { NOOP_MIDDLEWARE_FN } from '../../dist/core/middleware/noop-middleware.js'; import { Pipeline } from '../../dist/core/render/index.js'; import { RouteCache } from '../../dist/core/render/route-cache.js'; -import { unixify } from './correct-path.js'; /** @type {import('../../src/core/logger/core').Logger} */ export const defaultLogger = new Logger({ @@ -27,102 +25,21 @@ export const silentLogging = { level: 'error', }; -class VirtualVolume extends Volume { - #root = ''; - constructor(root) { - super(); - this.#root = root; - } - - #forcePath(p) { - if (p instanceof URL) { - p = unixify(fileURLToPath(p)); - } else { - p = unixify(p); - } - return p; - } - - getFullyResolvedPath(pth) { - return npath.posix.join(this.#root, pth); - } - - readFile(p, ...args) { - return super.readFile(this.#forcePath(p), ...args); - } - - existsSync(p) { - return super.existsSync(this.#forcePath(p)); - } - - writeFileFromRootSync(pth, ...rest) { - return super.writeFileSync(this.getFullyResolvedPath(pth), ...rest); - } -} - -class VirtualVolumeWithFallback extends VirtualVolume { - // Fallback to the real fs - readFile(p, ...args) { - const cb = args[args.length - 1]; - const argsMinusCallback = args.slice(0, args.length - 1); - return super.readFile(p, ...argsMinusCallback, function (err, data) { - if (err) { - realFS.readFile(p, ...argsMinusCallback, function (err2, data2) { - if (err2) { - cb(err); - } else { - cb(null, data2); - } - }); - } else { - cb(null, data); - } - }); - } - - readFileSync(p, ...args) { - try { - return super.readFileSync(p, ...args); - } catch { - return realFS.readFileSync(p, ...args); - } - } -} - -export function createFs(json, root, VolumeImpl = VirtualVolume) { - if (typeof root !== 'string') { - root = unixify(fileURLToPath(root)); - } - - const structure = {}; - for (const [key, value] of Object.entries(json)) { - const fullpath = npath.posix.join(root, key); - structure[fullpath] = value; - } - - const fs = new VolumeImpl(root); - fs.fromJSON(structure); - return fs; -} - -export function createFsWithFallback(json, root) { - return createFs(json, root, VirtualVolumeWithFallback); -} +const tempFixturesDir = fileURLToPath(new URL('./_temp-fixtures/', import.meta.url)); /** - * - * @param {import('../../src/core/dev/container').Container} container - * @param {typeof import('node:fs')} fs - * @param {string} shortPath - * @param {'change'} eventType + * @param {import('fs-fixture').FileTree} tree */ -export function triggerFSEvent(container, fs, shortPath, eventType) { - container.viteServer.watcher.emit(eventType, fs.getFullyResolvedPath(shortPath)); - - if (!fileURLToPath(container.settings.config.root).startsWith('/')) { - const drive = fileURLToPath(container.settings.config.root).slice(0, 2); - container.viteServer.watcher.emit(eventType, drive + fs.getFullyResolvedPath(shortPath)); - } +export async function createFixture(tree) { + return await _createFixture( + { + 'package.json': '{}', + ...tree, + }, + { + tempDir: tempFixturesDir, + }, + ); } export function createRequestAndResponse(reqOptions = {}) { diff --git a/packages/astro/test/units/vite-plugin-astro-server/request.test.js b/packages/astro/test/units/vite-plugin-astro-server/request.test.js index 26f446cb1..b99a2cb60 100644 --- a/packages/astro/test/units/vite-plugin-astro-server/request.test.js +++ b/packages/astro/test/units/vite-plugin-astro-server/request.test.js @@ -1,6 +1,5 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import { createContainer } from '../../../dist/core/dev/container.js'; import { createLoader } from '../../../dist/core/module-loader/index.js'; import { createRouteManifest } from '../../../dist/core/routing/index.js'; @@ -12,13 +11,13 @@ import testAdapter from '../../test-adapter.js'; import { createAstroModule, createBasicSettings, - createFs, + createFixture, createRequestAndResponse, defaultLogger, } from '../test-utils.js'; -async function createDevPipeline(overrides = {}) { - const settings = overrides.settings ?? (await createBasicSettings({ root: '/' })); +async function createDevPipeline(overrides = {}, root) { + const settings = overrides.settings ?? (await createBasicSettings({ root })); const loader = overrides.loader ?? createLoader(); const manifest = createDevelopmentManifest(settings); @@ -28,31 +27,31 @@ async function createDevPipeline(overrides = {}) { describe('vite-plugin-astro-server', () => { describe('request', () => { it('renders a request', async () => { - const pipeline = await createDevPipeline({ - loader: createLoader({ - import(id) { - if (id === '\0astro-internal:middleware') { - return { onRequest: (_, next) => next() }; - } - const Page = createComponent(() => { - return render`<div id="test">testing</div>`; - }); - return createAstroModule(Page); - }, - }), + const fixture = await createFixture({ + // Note that the content doesn't matter here because we are using a custom loader. + '/src/pages/index.astro': '', }); - const controller = createController({ loader: pipeline.loader }); - const { req, res, text } = createRequestAndResponse(); - const fs = createFs( + const pipeline = await createDevPipeline( { - // Note that the content doesn't matter here because we are using a custom loader. - '/src/pages/index.astro': '', + loader: createLoader({ + import(id) { + if (id === '\0astro-internal:middleware') { + return { onRequest: (_, next) => next() }; + } + const Page = createComponent(() => { + return render`<div id="test">testing</div>`; + }); + return createAstroModule(Page); + }, + }), }, - '/', + fixture.path, ); + const controller = createController({ loader: pipeline.loader }); + const { req, res, text } = createRequestAndResponse(); const manifestData = createRouteManifest( { - fsMod: fs, + cwd: fixture.path, settings: pipeline.settings, }, defaultLogger, @@ -82,7 +81,6 @@ describe('vite-plugin-astro-server', () => { let settings; before(async () => { - const root = new URL('../../fixtures/api-routes/', import.meta.url); const fileSystem = { '/src/pages/url.astro': `{Astro.request.url}`, '/src/pages/prerendered.astro': `--- @@ -90,14 +88,13 @@ describe('vite-plugin-astro-server', () => { --- {Astro.request.url}`, }; - const fs = createFs(fileSystem, root); + const fixture = await createFixture(fileSystem); settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', adapter: testAdapter(), }); container = await createContainer({ - fs, settings, logger: defaultLogger, }); diff --git a/packages/astro/test/units/vite-plugin-astro-server/response.test.js b/packages/astro/test/units/vite-plugin-astro-server/response.test.js index fadaed99f..2d256fb71 100644 --- a/packages/astro/test/units/vite-plugin-astro-server/response.test.js +++ b/packages/astro/test/units/vite-plugin-astro-server/response.test.js @@ -1,16 +1,14 @@ import * as assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { fileURLToPath } from 'node:url'; import { createContainer } from '../../../dist/core/dev/container.js'; import testAdapter from '../../test-adapter.js'; import { createBasicSettings, - createFs, + createFixture, createRequestAndResponse, defaultLogger, } from '../test-utils.js'; -const root = new URL('../../fixtures/api-routes/', import.meta.url); const fileSystem = { '/src/pages/index.js': `export const GET = () => { const headers = new Headers(); @@ -51,14 +49,13 @@ describe('endpoints', () => { let settings; before(async () => { - const fs = createFs(fileSystem, root); + const fixture = await createFixture(fileSystem); settings = await createBasicSettings({ - root: fileURLToPath(root), + root: fixture.path, output: 'server', adapter: testAdapter(), }); container = await createContainer({ - fs, settings, logger: defaultLogger, }); diff --git a/patches/fs-fixture@2.4.0.patch b/patches/fs-fixture@2.4.0.patch new file mode 100644 index 000000000..1ec3a3c4d --- /dev/null +++ b/patches/fs-fixture@2.4.0.patch @@ -0,0 +1,24 @@ +diff --git a/dist/index.d.mts b/dist/index.d.mts +index be5b88e034211892ba079c3c5c5c6f5d5f767cd4..55dcc0e99c66719d5fa68d4713b02c1919deae19 100644 +--- a/dist/index.d.mts ++++ b/dist/index.d.mts +@@ -61,6 +61,10 @@ type Api = ApiBase & { + type FileTree = { + [path: string]: string | FileTree | ((api: Api) => string | Symlink); + }; +-declare const createFixture: (source?: string | FileTree) => Promise<FsFixture>; ++type CreateFixtureOptions = { ++ // An absolute path to a different directory than `os.tmpdir()` ++ tempDir?: string ++} ++declare const createFixture: (source?: string | FileTree, opts?: CreateFixtureOptions) => Promise<FsFixture>; + +-export { type FileTree, FsFixture, createFixture }; ++export { type FileTree, FsFixture, CreateFixtureOptions, createFixture }; +diff --git a/dist/index.mjs b/dist/index.mjs +index cd6cab3beebf3f38fe4f1e2a9c58aff2b87258f7..ad24d852a357fd582f9e83ac20cb73bfbcb9bfc0 100755 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -1 +1 @@ +-import s from"fs/promises";import o from"path";import y from"fs";import m from"os";typeof Symbol.asyncDispose!="symbol"&&Object.defineProperty(Symbol,"asyncDispose",{configurable:!1,enumerable:!1,writable:!1,value:Symbol.for("asyncDispose")});class w{path;constructor(t){this.path=t}getPath(...t){return o.join(this.path,...t)}exists(t=""){return s.access(this.getPath(t)).then(()=>!0,()=>!1)}rm(t=""){return s.rm(this.getPath(t),{recursive:!0,force:!0})}writeFile(t,r){return s.writeFile(this.getPath(t),r)}writeJson(t,r){return this.writeFile(t,JSON.stringify(r,null,2))}readFile(t,r){return s.readFile(this.getPath(t),r)}async[Symbol.asyncDispose](){await this.rm()}}const g=y.realpathSync(m.tmpdir()),b=`fs-fixture-${Date.now()}`;let l=0;const P=()=>(l+=1,l);class h{target;type;path;constructor(t,r){this.target=t,this.type=r}}const u=(i,t,r)=>{const e=[];for(const n in i){if(!Object.hasOwn(i,n))continue;const c=o.join(t,n);let a=i[n];if(typeof a=="function"){const f=Object.assign(Object.create(r),{filePath:c}),p=a(f);if(p instanceof h){p.path=c,e.push(p);continue}else a=p}typeof a=="string"?e.push({path:c,content:a}):e.push(...u(a,c,r))}return e},d=async i=>{const t=o.join(g,`${b}-${P()}/`);if(await s.mkdir(t,{recursive:!0}),i){if(typeof i=="string")await s.cp(i,t,{recursive:!0});else if(typeof i=="object"){const r={fixturePath:t,getPath:(...e)=>o.join(t,...e),symlink:(e,n)=>new h(e,n)};await Promise.all(u(i,t,r).map(async e=>{await s.mkdir(o.dirname(e.path),{recursive:!0}),e instanceof h?await s.symlink(e.target,e.path,e.type):await s.writeFile(e.path,e.content)}))}}return new w(t)};export{d as createFixture}; ++import s from"fs/promises";import o from"path";import y from"fs";import m from"os";typeof Symbol.asyncDispose!="symbol"&&Object.defineProperty(Symbol,"asyncDispose",{configurable:!1,enumerable:!1,writable:!1,value:Symbol.for("asyncDispose")});class w{path;constructor(t){this.path=t}getPath(...t){return o.join(this.path,...t)}exists(t=""){return s.access(this.getPath(t)).then(()=>!0,()=>!1)}rm(t=""){return s.rm(this.getPath(t),{recursive:!0,force:!0})}writeFile(t,r){return s.writeFile(this.getPath(t),r)}writeJson(t,r){return this.writeFile(t,JSON.stringify(r,null,2))}readFile(t,r){return s.readFile(this.getPath(t),r)}async[Symbol.asyncDispose](){await this.rm()}}const g=y.realpathSync(m.tmpdir()),b=`fs-fixture-${Date.now()}`;let l=0;const P=()=>(l+=1,l);class h{target;type;path;constructor(t,r){this.target=t,this.type=r}}const u=(i,t,r)=>{const e=[];for(const n in i){if(!Object.hasOwn(i,n))continue;const c=o.join(t,n);let a=i[n];if(typeof a=="function"){const f=Object.assign(Object.create(r),{filePath:c}),p=a(f);if(p instanceof h){p.path=c,e.push(p);continue}else a=p}typeof a=="string"?e.push({path:c,content:a}):e.push(...u(a,c,r))}return e},d=async (i, opts)=>{const t=o.join(opts?.tempDir ?? g,`${b}-${P()}/`);if(await s.mkdir(t,{recursive:!0}),i){if(typeof i=="string")await s.cp(i,t,{recursive:!0});else if(typeof i=="object"){const r={fixturePath:t,getPath:(...e)=>o.join(t,...e),symlink:(e,n)=>new h(e,n)};await Promise.all(u(i,t,r).map(async e=>{await s.mkdir(o.dirname(e.path),{recursive:!0}),e instanceof h?await s.symlink(e.target,e.path,e.type):await s.writeFile(e.path,e.content)}))}}return new w(t)};export{d as createFixture}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad9ac3a17..78f2c5470 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: false excludeLinksFromLockfile: false +patchedDependencies: + fs-fixture@2.4.0: + hash: hvkuaks2ic76pdflr3iifgi4ku + path: patches/fs-fixture@2.4.0.patch + importers: .: @@ -703,15 +708,15 @@ importers: expect-type: specifier: ^1.1.0 version: 1.1.0 + fs-fixture: + specifier: ^2.4.0 + version: 2.4.0(patch_hash=hvkuaks2ic76pdflr3iifgi4ku) mdast-util-mdx: specifier: ^3.0.0 version: 3.0.0 mdast-util-mdx-jsx: specifier: ^3.1.3 version: 3.1.3 - memfs: - specifier: ^4.14.0 - version: 4.14.0 node-mocks-http: specifier: ^1.16.1 version: 1.16.1(@types/node@18.19.50) @@ -2665,12 +2670,6 @@ importers: specifier: workspace:* version: link:../../.. - packages/astro/test/fixtures/content-mixed-errors: - dependencies: - astro: - specifier: workspace:* - version: link:../../.. - packages/astro/test/fixtures/content-ssr-integration: dependencies: '@astrojs/mdx': @@ -4172,6 +4171,15 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/units/_temp-fixtures: + dependencies: + '@astrojs/mdx': + specifier: workspace:* + version: link:../../../../integrations/mdx + astro: + specifier: workspace:* + version: link:../../.. + packages/create-astro: dependencies: '@astrojs/cli-kit': @@ -6528,24 +6536,6 @@ packages: resolution: {integrity: sha512-n5JEf16Wr4mdkRMZ8wMP/wN9/sHmTjRPbouXjJH371mZ2LEGDl72t8tEsMRNFerQN/QJtivOxqK1frdGa4QK5Q==} engines: {node: '>=10'} - '@jsonjoy.com/base64@1.1.2': - resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - - '@jsonjoy.com/json-pack@1.1.0': - resolution: {integrity: sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - - '@jsonjoy.com/util@1.3.0': - resolution: {integrity: sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@libsql/client@0.14.0': resolution: {integrity: sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==} @@ -8274,6 +8264,10 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} + fs-fixture@2.4.0: + resolution: {integrity: sha512-aFoTWGj288IEOdXBeesdcbdMvRtExbqpOp1SCjE3nRdlT7vBBCD6bf76C9FCq8/6pIPSo56P7+HeT9zT/n8rMA==} + engines: {node: '>=18.0.0'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -8475,10 +8469,6 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - hyperdyperid@1.2.0: - resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} - engines: {node: '>=10.18'} - hyperid@3.3.0: resolution: {integrity: sha512-7qhCVT4MJIoEsNcbhglhdmBKb09QtcmJNiIQGq7js/Khf5FtQQ9bzcAuloeqBeee7XD7JqDeve9KNlQya5tSGQ==} @@ -8924,10 +8914,6 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - memfs@4.14.0: - resolution: {integrity: sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==} - engines: {node: '>= 4.0.0'} - merge-anything@5.1.7: resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} engines: {node: '>=12.13'} @@ -10243,12 +10229,6 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thingies@1.21.0: - resolution: {integrity: sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==} - engines: {node: '>=10.18'} - peerDependencies: - tslib: ^2 - timestring@6.0.0: resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==} engines: {node: '>=8'} @@ -10302,12 +10282,6 @@ packages: resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} engines: {node: '>=18'} - tree-dump@1.0.2: - resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -11997,22 +11971,6 @@ snapshots: '@jsdevtools/rehype-toc@3.0.2': {} - '@jsonjoy.com/base64@1.1.2(tslib@2.7.0)': - dependencies: - tslib: 2.7.0 - - '@jsonjoy.com/json-pack@1.1.0(tslib@2.7.0)': - dependencies: - '@jsonjoy.com/base64': 1.1.2(tslib@2.7.0) - '@jsonjoy.com/util': 1.3.0(tslib@2.7.0) - hyperdyperid: 1.2.0 - thingies: 1.21.0(tslib@2.7.0) - tslib: 2.7.0 - - '@jsonjoy.com/util@1.3.0(tslib@2.7.0)': - dependencies: - tslib: 2.7.0 - '@libsql/client@0.14.0': dependencies: '@libsql/core': 0.14.0 @@ -13833,6 +13791,8 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 + fs-fixture@2.4.0(patch_hash=hvkuaks2ic76pdflr3iifgi4ku): {} + fsevents@2.3.2: optional: true @@ -14145,8 +14105,6 @@ snapshots: human-signals@5.0.0: {} - hyperdyperid@1.2.0: {} - hyperid@3.3.0: dependencies: buffer: 5.7.1 @@ -14709,13 +14667,6 @@ snapshots: media-typer@0.3.0: {} - memfs@4.14.0: - dependencies: - '@jsonjoy.com/json-pack': 1.1.0(tslib@2.7.0) - '@jsonjoy.com/util': 1.3.0(tslib@2.7.0) - tree-dump: 1.0.2(tslib@2.7.0) - tslib: 2.7.0 - merge-anything@5.1.7: dependencies: is-what: 4.1.16 @@ -16344,10 +16295,6 @@ snapshots: dependencies: any-promise: 1.3.0 - thingies@1.21.0(tslib@2.7.0): - dependencies: - tslib: 2.7.0 - timestring@6.0.0: {} tinybench@2.9.0: {} @@ -16387,10 +16334,6 @@ snapshots: dependencies: punycode: 2.3.1 - tree-dump@1.0.2(tslib@2.7.0): - dependencies: - tslib: 2.7.0 - trim-lines@3.0.1: {} trough@2.2.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index be78c826e..22dae231e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,9 +1,11 @@ packages: - 'packages/**/*' + - 'packages/astro/test/units/_temp-fixtures' - 'examples/**/*' - 'smoke/**/*' - 'scripts' - 'benchmark' - 'benchmark/packages/*' # Below excludes are only for Turbo because it doesn't respect gitignore like pnpm does + - '!packages/astro/test/units/_temp-fixtures/*' - '!**/.vercel/**' diff --git a/scripts/cmd/test.js b/scripts/cmd/test.js index 3182c4b90..3b266ff1c 100644 --- a/scripts/cmd/test.js +++ b/scripts/cmd/test.js @@ -25,6 +25,8 @@ export default async function test() { timeout: { type: 'string', alias: 't' }, // Test setup file setup: { type: 'string', alias: 's' }, + // Test teardown file + teardown: { type: 'string' }, }, }); @@ -59,6 +61,10 @@ export default async function test() { files.push(tempTestFile); } + const teardownModule = args.values.teardown + ? await import(pathToFileURL(path.resolve(args.values.teardown)).toString()) + : undefined; + // https://nodejs.org/api/test.html#runoptions run({ files, @@ -74,6 +80,10 @@ export default async function test() { // so we set it here manually process.exitCode = 1; }) + .on('end', () => { + const testPassed = process.exitCode === 0 || process.exitCode === undefined; + teardownModule?.default(testPassed); + }) .pipe(new spec()) .pipe(process.stdout); } |