diff --git a/packages/astro/test/astro-basic.test.js b/packages/astro/test/astro-basic.test.js deleted file mode 100644 index 70b29a73b913..000000000000 --- a/packages/astro/test/astro-basic.test.js +++ /dev/null @@ -1,265 +0,0 @@ -import assert from 'node:assert/strict'; -import { after, before, describe, it } from 'node:test'; -import * as cheerio from 'cheerio'; -import { loadFixture } from './test-utils.js'; -import createTestPrerenderer from './test-prerenderer.js'; - -describe('Astro basic build', () => { - /** @type {import('./test-utils').Fixture} */ - let fixture; - - let previewServer; - - before(async () => { - fixture = await loadFixture({ - root: './fixtures/astro-basic/', - }); - await fixture.build(); - previewServer = await fixture.preview(); - }); - - // important: close preview server (free up port and connection) - after(async () => { - await previewServer.stop(); - }); - - it('Can load page', async () => { - const html = await fixture.readFile(`/index.html`); - const $ = cheerio.load(html); - - assert.equal($('h1').text(), 'Hello world!'); - }); - - it('Correctly serializes boolean attributes', async () => { - const html = await fixture.readFile('/index.html'); - const $ = cheerio.load(html); - - assert.equal($('h1').attr('data-something'), ''); - assert.equal($('h2').attr('not-data-ok'), ''); - }); - - it('Selector with an empty body', async () => { - const html = await fixture.readFile('/empty-class/index.html'); - const $ = cheerio.load(html); - - assert.equal($('.author').length, 1); - }); - - it('Allows forward-slashes in mustache tags (#407)', async () => { - const html = await fixture.readFile('/forward-slash/index.html'); - const $ = cheerio.load(html); - - assert.equal($('a[href="/post/one"]').length, 1); - assert.equal($('a[href="/post/two"]').length, 1); - assert.equal($('a[href="/post/three"]').length, 1); - }); - - it('Allows spread attributes (#521)', async () => { - const html = await fixture.readFile('/spread/index.html'); - const $ = cheerio.load(html); - - assert.equal($('#spread-leading').length, 1); - assert.equal($('#spread-leading').attr('a'), '0'); - assert.equal($('#spread-leading').attr('b'), '1'); - assert.equal($('#spread-leading').attr('c'), '2'); - - assert.equal($('#spread-trailing').length, 1); - assert.equal($('#spread-trailing').attr('a'), '0'); - assert.equal($('#spread-trailing').attr('b'), '1'); - assert.equal($('#spread-trailing').attr('c'), '2'); - }); - - it('Allows spread attributes with TypeScript (#521)', async () => { - const html = await fixture.readFile('/spread/index.html'); - const $ = cheerio.load(html); - - assert.equal($('#spread-ts').length, 1); - assert.equal($('#spread-ts').attr('a'), '0'); - assert.equal($('#spread-ts').attr('b'), '1'); - assert.equal($('#spread-ts').attr('c'), '2'); - }); - - it('Allows scoped classes with spread', async () => { - const html = await fixture.readFile('/spread-scope/index.html'); - const $ = cheerio.load(html); - - assert.equal($('#spread-plain').length, 1); - assert.match($('#spread-plain').attr('class'), /astro-.*/); - - assert.equal($('#spread-class').length, 1); - assert.match($('#spread-class').attr('class'), /astro-.*/); - - assert.equal($('#spread-class-list').length, 1); - assert.match($('#spread-class-list').attr('class'), /astro-.*/); - }); - - it('Allows using the Fragment element to be used', async () => { - const html = await fixture.readFile('/fragment/index.html'); - const $ = cheerio.load(html); - - // will be 1 if element rendered correctly - assert.equal($('#one').length, 1); - }); - - it('supports special chars in filename', async () => { - // will have already erred by now, but add test anyway - assert.ok(await fixture.readFile('/special-“characters” -in-file/index.html')); - }); - - it('renders the components top-down', async () => { - const html = await fixture.readFile('/order/index.html'); - const $ = cheerio.load(html); - assert.equal($('#rendered-order').text(), 'Rendered order: A, B'); - }); - - it('renders markdown in utf-8 by default', async () => { - const html = await fixture.readFile('/chinese-encoding-md/index.html'); - const $ = cheerio.load(html); - assert.equal($('h1').text(), '我的第一篇博客文章'); - assert.match(html, / { - const html = await fixture.readFile('/input/index.html'); - const $ = cheerio.load(html); - - // - assert.equal($('body > :nth-child(1)').prop('outerHTML'), ''); - - // - assert.equal($('body > :nth-child(2)').prop('outerHTML'), ''); - - // - assert.equal($('body > :nth-child(3)').prop('outerHTML'), ''); - - // - assert.equal( - $('body > :nth-child(4)').prop('outerHTML'), - '', - ); - - // textarea - assert.equal($('body > :nth-child(5)').prop('outerHTML'), ''); - }); - - it('Generates pages that end with .mjs', async () => { - const content1 = await fixture.readFile('/get-static-paths-with-mjs/example.mjs'); - assert.ok(content1); - const content2 = await fixture.readFile('/get-static-paths-with-mjs/example.js'); - assert.ok(content2); - }); - - it('allows file:// urls as module specifiers', async () => { - const html = await fixture.readFile('/fileurl/index.html'); - const $ = cheerio.load(html); - assert.equal($('h1').text(), 'WORKS'); - }); - - it('Handles importing .astro?raw correctly', async () => { - const html = await fixture.readFile('/import-queries/raw/index.html'); - const $ = cheerio.load(html); - const rawValue = $('.raw-value').text(); - assert.match(rawValue, /

Hello<\/h1>/); - assert.match(rawValue, / - + \ No newline at end of file diff --git a/packages/astro/test/fixtures/astro-basic/astro.config.mjs b/packages/astro/test/fixtures/static/astro.config.mjs similarity index 100% rename from packages/astro/test/fixtures/astro-basic/astro.config.mjs rename to packages/astro/test/fixtures/static/astro.config.mjs diff --git a/packages/astro/test/fixtures/astro-basic/my-config.mjs b/packages/astro/test/fixtures/static/my-config.mjs similarity index 100% rename from packages/astro/test/fixtures/astro-basic/my-config.mjs rename to packages/astro/test/fixtures/static/my-config.mjs diff --git a/packages/astro/test/fixtures/astro-basic/package.json b/packages/astro/test/fixtures/static/package.json similarity index 86% rename from packages/astro/test/fixtures/astro-basic/package.json rename to packages/astro/test/fixtures/static/package.json index 568028dce239..6b27d3f996e9 100644 --- a/packages/astro/test/fixtures/astro-basic/package.json +++ b/packages/astro/test/fixtures/static/package.json @@ -1,5 +1,5 @@ { - "name": "@test/astro-basic", + "name": "@test/static", "version": "0.0.0", "private": true, "dependencies": { diff --git a/packages/astro/test/fixtures/astro-basic/src/components/Input.astro b/packages/astro/test/fixtures/static/src/components/Input.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/components/Input.astro rename to packages/astro/test/fixtures/static/src/components/Input.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/components/OrderA.astro b/packages/astro/test/fixtures/static/src/components/OrderA.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/components/OrderA.astro rename to packages/astro/test/fixtures/static/src/components/OrderA.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/components/OrderB.astro b/packages/astro/test/fixtures/static/src/components/OrderB.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/components/OrderB.astro rename to packages/astro/test/fixtures/static/src/components/OrderB.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/components/OrderLast.astro b/packages/astro/test/fixtures/static/src/components/OrderLast.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/components/OrderLast.astro rename to packages/astro/test/fixtures/static/src/components/OrderLast.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/components/Tour.jsx b/packages/astro/test/fixtures/static/src/components/Tour.jsx similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/components/Tour.jsx rename to packages/astro/test/fixtures/static/src/components/Tour.jsx diff --git a/packages/astro/test/fixtures/astro-basic/src/layouts/base.astro b/packages/astro/test/fixtures/static/src/layouts/base.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/layouts/base.astro rename to packages/astro/test/fixtures/static/src/layouts/base.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-md.md b/packages/astro/test/fixtures/static/src/pages/chinese-encoding-md.md similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/chinese-encoding-md.md rename to packages/astro/test/fixtures/static/src/pages/chinese-encoding-md.md diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/client.astro b/packages/astro/test/fixtures/static/src/pages/client.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/client.astro rename to packages/astro/test/fixtures/static/src/pages/client.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/empty-class.astro b/packages/astro/test/fixtures/static/src/pages/empty-class.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/empty-class.astro rename to packages/astro/test/fixtures/static/src/pages/empty-class.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/fileurl.astro b/packages/astro/test/fixtures/static/src/pages/fileurl.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/fileurl.astro rename to packages/astro/test/fixtures/static/src/pages/fileurl.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/forward-slash.astro b/packages/astro/test/fixtures/static/src/pages/forward-slash.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/forward-slash.astro rename to packages/astro/test/fixtures/static/src/pages/forward-slash.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/fragment.astro b/packages/astro/test/fixtures/static/src/pages/fragment.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/fragment.astro rename to packages/astro/test/fixtures/static/src/pages/fragment.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/get-static-paths-with-mjs/[...file].js b/packages/astro/test/fixtures/static/src/pages/get-static-paths-with-mjs/[...file].js similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/get-static-paths-with-mjs/[...file].js rename to packages/astro/test/fixtures/static/src/pages/get-static-paths-with-mjs/[...file].js diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/import-queries/_content.astro b/packages/astro/test/fixtures/static/src/pages/import-queries/_content.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/import-queries/_content.astro rename to packages/astro/test/fixtures/static/src/pages/import-queries/_content.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/import-queries/raw.astro b/packages/astro/test/fixtures/static/src/pages/import-queries/raw.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/import-queries/raw.astro rename to packages/astro/test/fixtures/static/src/pages/import-queries/raw.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/index.astro b/packages/astro/test/fixtures/static/src/pages/index.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/index.astro rename to packages/astro/test/fixtures/static/src/pages/index.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/input.astro b/packages/astro/test/fixtures/static/src/pages/input.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/input.astro rename to packages/astro/test/fixtures/static/src/pages/input.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/nested-astro/index.astro b/packages/astro/test/fixtures/static/src/pages/nested-astro/index.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/nested-astro/index.astro rename to packages/astro/test/fixtures/static/src/pages/nested-astro/index.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/nested-md/index.md b/packages/astro/test/fixtures/static/src/pages/nested-md/index.md similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/nested-md/index.md rename to packages/astro/test/fixtures/static/src/pages/nested-md/index.md diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/news.astro b/packages/astro/test/fixtures/static/src/pages/news.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/news.astro rename to packages/astro/test/fixtures/static/src/pages/news.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/order.astro b/packages/astro/test/fixtures/static/src/pages/order.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/order.astro rename to packages/astro/test/fixtures/static/src/pages/order.astro diff --git "a/packages/astro/test/fixtures/astro-basic/src/pages/special-\342\200\234characters\342\200\235 -in-file.md" "b/packages/astro/test/fixtures/static/src/pages/special-\342\200\234characters\342\200\235 -in-file.md" similarity index 100% rename from "packages/astro/test/fixtures/astro-basic/src/pages/special-\342\200\234characters\342\200\235 -in-file.md" rename to "packages/astro/test/fixtures/static/src/pages/special-\342\200\234characters\342\200\235 -in-file.md" diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/spread-scope.astro b/packages/astro/test/fixtures/static/src/pages/spread-scope.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/spread-scope.astro rename to packages/astro/test/fixtures/static/src/pages/spread-scope.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/pages/spread.astro b/packages/astro/test/fixtures/static/src/pages/spread.astro similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/pages/spread.astro rename to packages/astro/test/fixtures/static/src/pages/spread.astro diff --git a/packages/astro/test/fixtures/astro-basic/src/strings.js b/packages/astro/test/fixtures/static/src/strings.js similarity index 100% rename from packages/astro/test/fixtures/astro-basic/src/strings.js rename to packages/astro/test/fixtures/static/src/strings.js diff --git a/packages/astro/test/shared-fixture.js b/packages/astro/test/shared-fixture.js new file mode 100644 index 000000000000..0346ae062384 --- /dev/null +++ b/packages/astro/test/shared-fixture.js @@ -0,0 +1,186 @@ +import { fileURLToPath } from 'node:url'; +import { loadFixture as baseLoadFixture } from './test-utils.js'; + +/** + * Registry for shared fixtures to avoid creating multiple fixture instances + * when running multiple test files in the same process. + */ +const fixtureRegistry = new Map(); +const loadPromises = new Map(); +const buildPromises = new Map(); + +/** + * Get or create a shared fixture. If a fixture with the same root path has already been + * loaded, it will be reused. Otherwise, a new fixture will be created. + * + * The fixture is NOT automatically built - call fixture.build() when needed. + * + * @param {Object} options + * @param {string} options.root - Root directory for the fixture + * @param {Object} options.config - Additional config options for the fixture + * @returns {Promise} + */ +export async function getSharedFixture({ root, ...config }) { + if (!root) { + throw new Error('Shared fixture must have a root path'); + } + + // Resolve the root path to an absolute path + const resolvedRoot = fileURLToPath(new URL(root, import.meta.url)); + + // Use the resolved path as the cache key + const cacheKey = resolvedRoot; + + // Check if we already have this fixture + if (fixtureRegistry.has(cacheKey)) { + return fixtureRegistry.get(cacheKey); + } + + // Check if another test is already loading this fixture + if (loadPromises.has(cacheKey)) { + await loadPromises.get(cacheKey); + return fixtureRegistry.get(cacheKey); + } + + // Create the fixture (but don't build it) + const loadPromise = (async () => { + const fixture = await baseLoadFixture({ root, ...config }); + + // Override the build method to ensure it's only built once + const originalBuild = fixture.build.bind(fixture); + fixture.build = async function () { + if (buildPromises.has(cacheKey)) { + // Another test is already building, wait for it + return buildPromises.get(cacheKey); + } + + if (fixture._built) { + // Already built, no-op + return; + } + + // Start the build + const buildPromise = originalBuild(); + buildPromises.set(cacheKey, buildPromise); + + try { + await buildPromise; + fixture._built = true; + } finally { + buildPromises.delete(cacheKey); + } + }; + + // Add a flag to indicate this is a shared fixture + fixture._isShared = true; + fixture._sharedCacheKey = cacheKey; + + // Store the fixture + fixtureRegistry.set(cacheKey, fixture); + + return fixture; + })(); + + loadPromises.set(cacheKey, loadPromise); + + try { + const fixture = await loadPromise; + return fixture; + } finally { + // Clean up the load promise + loadPromises.delete(cacheKey); + } +} + +/** + * Get a shared preview server for a fixture. This ensures only one preview server + * is started per shared fixture. + */ +const previewServers = new Map(); + +export async function getSharedPreviewServer(fixture) { + if (!fixture._isShared) { + throw new Error('getSharedPreviewServer can only be used with shared fixtures'); + } + + const cacheKey = fixture._sharedCacheKey; + + if (previewServers.has(cacheKey)) { + return previewServers.get(cacheKey); + } + + const server = await fixture.preview(); + previewServers.set(cacheKey, server); + return server; +} + +/** + * Get a shared dev server for a fixture. This ensures only one dev server + * is started per shared fixture. + */ +const devServers = new Map(); + +export async function getSharedDevServer(fixture) { + if (!fixture._isShared) { + throw new Error('getSharedDevServer can only be used with shared fixtures'); + } + + const cacheKey = fixture._sharedCacheKey; + + if (devServers.has(cacheKey)) { + return devServers.get(cacheKey); + } + + const server = await fixture.startDevServer(); + devServers.set(cacheKey, server); + return server; +} + +/** + * Stop all shared preview servers + */ +async function stopAllPreviewServers() { + const stopPromises = []; + for (const [name, server] of previewServers) { + stopPromises.push( + server.stop().catch((err) => { + console.error(`Error stopping preview server for ${name}:`, err); + }), + ); + } + await Promise.all(stopPromises); + previewServers.clear(); +} + +/** + * Shared fixture system for Astro tests + * + * This module provides utilities to share built fixtures across multiple test files + * to reduce total test execution time. + * + * LIMITATIONS: + * - Cannot test multiple build configurations with the same fixture + * - All tests using a shared fixture must use identical config settings + * - Tests that need to verify different build outputs (with/without base path, + * different asset configurations, etc.) cannot use shared fixtures + * - Dev/preview servers are shared - tests must be careful about isolation + */ +async function stopAllDevServers() { + const stopPromises = []; + for (const [name, server] of devServers) { + stopPromises.push( + server.stop().catch((err) => { + console.error(`Error stopping dev server for ${name}:`, err); + }), + ); + } + await Promise.all(stopPromises); + devServers.clear(); +} + +/** + * Stop all shared servers (both preview and dev) + */ +export async function stopAllServers() { + await Promise.all([stopAllPreviewServers(), stopAllDevServers()]); +} diff --git a/packages/astro/test/ssr-script.test.js b/packages/astro/test/ssr-script.test.js deleted file mode 100644 index 755c5061f526..000000000000 --- a/packages/astro/test/ssr-script.test.js +++ /dev/null @@ -1,241 +0,0 @@ -import assert from 'node:assert/strict'; -import { before, describe, it } from 'node:test'; -import { load as cheerioLoad } from 'cheerio'; -import testAdapter from './test-adapter.js'; -import { loadFixture } from './test-utils.js'; - -async function fetchHTML(fixture, path) { - const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com' + path); - const response = await app.render(request); - const html = await response.text(); - return html; -} - -/** @type {import('./test-utils.js').AstroInlineConfig} */ -const defaultFixtureOptions = { - root: './fixtures/ssr-script/', - output: 'server', - adapter: testAdapter(), -}; - -describe('Inline scripts in SSR', () => { - /** @type {import('./test-utils.js').Fixture} */ - let fixture; - - describe('without base path', () => { - before(async () => { - fixture = await loadFixture({ - ...defaultFixtureOptions, - outDir: './dist/inline-scripts-without-base-path', - }); - await fixture.build(); - }); - - it('scripts get included', async () => { - const html = await fetchHTML(fixture, '/'); - const $ = cheerioLoad(html); - assert.equal($('script').length, 1); - }); - }); - - describe('with base path', () => { - const base = '/hello'; - - before(async () => { - fixture = await loadFixture({ - ...defaultFixtureOptions, - outDir: './dist/inline-scripts-with-base-path', - base, - }); - await fixture.build(); - }); - - it('Inlined scripts get included without base path in the script', async () => { - const html = await fetchHTML(fixture, '/hello/'); - const $ = cheerioLoad(html); - assert.equal($('script').html(), 'console.log("hello world");'); - }); - }); -}); - -describe('External scripts in SSR', () => { - /** @type {import('./test-utils.js').Fixture} */ - let fixture; - - describe('without base path', () => { - before(async () => { - fixture = await loadFixture({ - ...defaultFixtureOptions, - outDir: './dist/external-scripts-without-base-path', - vite: { - build: { - assetsInlineLimit: 0, - }, - }, - }); - await fixture.build(); - }); - - it('script has correct path', async () => { - const html = await fetchHTML(fixture, '/'); - const $ = cheerioLoad(html); - assert.match($('script').attr('src'), /^\/_astro\/.*\.js$/); - }); - }); - - describe('with base path', () => { - before(async () => { - fixture = await loadFixture({ - ...defaultFixtureOptions, - outDir: './dist/external-scripts-with-base-path', - vite: { - build: { - assetsInlineLimit: 0, - }, - }, - base: '/hello', - }); - await fixture.build(); - }); - - it('script has correct path', async () => { - const html = await fetchHTML(fixture, '/hello/'); - const $ = cheerioLoad(html); - assert.match($('script').attr('src'), /^\/hello\/_astro\/.*\.js$/); - }); - }); - - describe('with assetsPrefix', () => { - before(async () => { - fixture = await loadFixture({ - ...defaultFixtureOptions, - outDir: './dist/with-assets-prefix', - build: { - assetsPrefix: 'https://cdn.example.com', - }, - vite: { - build: { - assetsInlineLimit: 0, - }, - }, - }); - await fixture.build(); - }); - - it('script has correct path', async () => { - const html = await fetchHTML(fixture, '/'); - const $ = cheerioLoad(html); - assert.match($('script').attr('src'), /^https:\/\/cdn\.example\.com\/_astro\/.*\.js$/); - }); - }); - - describe('with custom rollup output file names', () => { - before(async () => { - fixture = await loadFixture({ - ...defaultFixtureOptions, - outDir: './dist/with-rollup-output-file-names', - vite: { - build: { - assetsInlineLimit: 0, - }, - environments: { - client: { - build: { - rollupOptions: { - output: { - entryFileNames: 'assets/entry.[hash].mjs', - chunkFileNames: 'assets/chunks/chunk.[hash].mjs', - assetFileNames: 'assets/asset.[hash][extname]', - }, - }, - }, - }, - }, - }, - }); - await fixture.build(); - }); - - it('script has correct path', async () => { - const html = await fetchHTML(fixture, '/'); - const $ = cheerioLoad(html); - assert.match($('script').attr('src'), /^\/assets\/entry\..{8}\.mjs$/); - }); - }); - - describe('with custom rollup output file names and base', () => { - before(async () => { - fixture = await loadFixture({ - ...defaultFixtureOptions, - outDir: './dist/with-rollup-output-file-names-and-base', - vite: { - build: { - assetsInlineLimit: 0, - }, - environments: { - client: { - build: { - rollupOptions: { - output: { - entryFileNames: 'assets/entry.[hash].mjs', - chunkFileNames: 'assets/chunks/chunk.[hash].mjs', - assetFileNames: 'assets/asset.[hash][extname]', - }, - }, - }, - }, - }, - }, - base: '/hello', - }); - await fixture.build(); - }); - - it('script has correct path', async () => { - const html = await fetchHTML(fixture, '/hello/'); - const $ = cheerioLoad(html); - assert.match($('script').attr('src'), /^\/hello\/assets\/entry\..{8}\.mjs$/); - }); - }); - - describe('with custom rollup output file names and assetsPrefix', () => { - before(async () => { - fixture = await loadFixture({ - ...defaultFixtureOptions, - outDir: './dist/with-rollup-output-file-names-and-assets-prefix', - build: { - assetsPrefix: 'https://cdn.example.com', - }, - vite: { - build: { - assetsInlineLimit: 0, - }, - environments: { - client: { - build: { - rollupOptions: { - output: { - entryFileNames: 'assets/entry.[hash].mjs', - chunkFileNames: 'assets/chunks/chunk.[hash].mjs', - assetFileNames: 'assets/asset.[hash][extname]', - }, - }, - }, - }, - }, - }, - }); - await fixture.build(); - }); - - it('script has correct path', async () => { - const html = await fetchHTML(fixture, '/'); - const $ = cheerioLoad(html); - assert.match( - $('script').attr('src'), - /^https:\/\/cdn\.example\.com\/assets\/entry\..{8}\.mjs$/, - ); - }); - }); -}); diff --git a/packages/astro/test/ssr.test.js b/packages/astro/test/ssr.test.js new file mode 100644 index 000000000000..a00295e953d6 --- /dev/null +++ b/packages/astro/test/ssr.test.js @@ -0,0 +1,54 @@ +import assert from 'node:assert/strict'; +import { before, describe, it } from 'node:test'; +import { load as cheerioLoad } from 'cheerio'; +import testAdapter from './test-adapter.js'; +import { getSharedFixture } from './shared-fixture.js'; + +/** + * Consolidated test suite for SSR tests + * This consolidates multiple smaller SSR test files to reduce total test execution time + */ + +const base = '/hello'; + +async function fetchHTML(fixture, path) { + const app = await fixture.loadTestAdapterApp(); + const request = new Request('http://example.com' + path); + const response = await app.render(request); + const html = await response.text(); + return html; +} + +describe('SSR Tests', () => { + /** @type {import('./test-utils.js').Fixture} */ + let fixture; + + before(async () => { + fixture = await getSharedFixture({ + root: './fixtures/ssr/', + output: 'server', + adapter: testAdapter(), + base, + }); + await fixture.build(); + }); + + describe('Scripts in SSR', () => { + it('inline scripts get included', async () => { + const html = await fetchHTML(fixture, base + '/scripts/inline'); + const $ = cheerioLoad(html); + assert.equal($('script').length, 1); + }); + + it('inline scripts get included without base path in the script content', async () => { + const html = await fetchHTML(fixture, base + '/scripts/inline'); + const $ = cheerioLoad(html); + assert.equal($('script').html(), 'console.log("hello world");'); + }); + + // Note: External script tests (testing inline scripts with assetsInlineLimit: 0) + // cannot be included here because shared fixtures don't support different build configs + }); + + // Additional SSR tests can be added here as we consolidate more files +}); diff --git a/packages/astro/test/static-basic.test.js b/packages/astro/test/static-basic.test.js new file mode 100644 index 000000000000..e1a78247e11d --- /dev/null +++ b/packages/astro/test/static-basic.test.js @@ -0,0 +1,73 @@ +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; +import { getSharedFixture, getSharedPreviewServer, stopAllServers } from './shared-fixture.js'; + +describe('Static - Basic Astro Features', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await getSharedFixture({ + root: './fixtures/static/', + }); + await fixture.build(); + await getSharedPreviewServer(fixture); + }); + + after(async () => { + await stopAllServers(); + }); + + it('Can load page', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Hello world!'); + }); + + it('Correctly serializes boolean attributes', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').attr('data-something'), ''); + assert.equal($('h2').attr('not-data-ok'), ''); + }); + + it('Selector with an empty body', async () => { + const html = await fixture.readFile('/empty-class/index.html'); + const $ = cheerio.load(html); + assert.equal($('.author').length, 1); + }); + + it('Allows forward-slashes in mustache tags', async () => { + const html = await fixture.readFile('/forward-slash/index.html'); + const $ = cheerio.load(html); + assert.equal($('a[href="/post/one"]').length, 1); + assert.equal($('a[href="/post/two"]').length, 1); + assert.equal($('a[href="/post/three"]').length, 1); + }); + + it('renders components top-down', async () => { + const html = await fixture.readFile('/order/index.html'); + const $ = cheerio.load(html); + assert.equal($('#rendered-order').text(), 'Rendered order: A, B'); + }); + + it('renders markdown in utf-8 by default', async () => { + const html = await fixture.readFile('/chinese-encoding-md/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), '我的第一篇博客文章'); + assert.match(html, / { + const html = await fixture.readFile('/fileurl/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'WORKS'); + }); + + it('server sourcemaps not included in output', async () => { + const files = await fixture.readdir('/'); + const hasSourcemaps = files.some((fileName) => fileName.endsWith('.map')); + assert.equal(hasSourcemaps, false, 'no sourcemap files in output'); + }); +}); diff --git a/packages/astro/test/static-spread.test.js b/packages/astro/test/static-spread.test.js new file mode 100644 index 000000000000..e987e829bc70 --- /dev/null +++ b/packages/astro/test/static-spread.test.js @@ -0,0 +1,60 @@ +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; +import { getSharedFixture, getSharedPreviewServer, stopAllServers } from './shared-fixture.js'; + +describe('Static - Spread Attributes', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await getSharedFixture({ + root: './fixtures/static/', + }); + await fixture.build(); + await getSharedPreviewServer(fixture); + }); + + after(async () => { + await stopAllServers(); + }); + + it('Allows spread attributes', async () => { + const html = await fixture.readFile('/spread/index.html'); + const $ = cheerio.load(html); + + assert.equal($('#spread-leading').length, 1); + assert.equal($('#spread-leading').attr('a'), '0'); + assert.equal($('#spread-leading').attr('b'), '1'); + assert.equal($('#spread-leading').attr('c'), '2'); + + assert.equal($('#spread-trailing').length, 1); + assert.equal($('#spread-trailing').attr('a'), '0'); + assert.equal($('#spread-trailing').attr('b'), '1'); + assert.equal($('#spread-trailing').attr('c'), '2'); + }); + + it('Allows spread attributes with TypeScript', async () => { + const html = await fixture.readFile('/spread/index.html'); + const $ = cheerio.load(html); + + assert.equal($('#spread-ts').length, 1); + assert.equal($('#spread-ts').attr('a'), '0'); + assert.equal($('#spread-ts').attr('b'), '1'); + assert.equal($('#spread-ts').attr('c'), '2'); + }); + + it('Allows scoped classes with spread', async () => { + const html = await fixture.readFile('/spread-scope/index.html'); + const $ = cheerio.load(html); + + assert.equal($('#spread-plain').length, 1); + assert.match($('#spread-plain').attr('class'), /astro-.*/); + + assert.equal($('#spread-class').length, 1); + assert.match($('#spread-class').attr('class'), /astro-.*/); + + assert.equal($('#spread-class-list').length, 1); + assert.match($('#spread-class-list').attr('class'), /astro-.*/); + }); +}); diff --git a/packages/astro/test/static.test.js b/packages/astro/test/static.test.js new file mode 100644 index 000000000000..50411dd64a88 --- /dev/null +++ b/packages/astro/test/static.test.js @@ -0,0 +1,219 @@ +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; +import { + getSharedFixture, + getSharedPreviewServer, + getSharedDevServer, + stopAllServers, +} from './shared-fixture.js'; + +/** + * Consolidated test suite for static/SSG tests + * This consolidates multiple smaller test files to reduce total test execution time + */ +describe('Static Tests', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await getSharedFixture({ + root: './fixtures/static/', + }); + await getSharedPreviewServer(fixture); + }); + + after(async () => { + // Shared fixtures handle their own cleanup + await stopAllServers(); + }); + + // Tests from astro-basic.test.js + describe('Basic Astro Features', () => { + it('Can load page', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Hello world!'); + }); + + it('Correctly serializes boolean attributes', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').attr('data-something'), ''); + assert.equal($('h2').attr('not-data-ok'), ''); + }); + + it('Selector with an empty body', async () => { + const html = await fixture.readFile('/empty-class/index.html'); + const $ = cheerio.load(html); + assert.equal($('.author').length, 1); + }); + + it('Allows forward-slashes in mustache tags', async () => { + const html = await fixture.readFile('/forward-slash/index.html'); + const $ = cheerio.load(html); + assert.equal($('a[href="/post/one"]').length, 1); + assert.equal($('a[href="/post/two"]').length, 1); + assert.equal($('a[href="/post/three"]').length, 1); + }); + + it('Allows spread attributes', async () => { + const html = await fixture.readFile('/spread/index.html'); + const $ = cheerio.load(html); + + assert.equal($('#spread-leading').length, 1); + assert.equal($('#spread-leading').attr('a'), '0'); + assert.equal($('#spread-leading').attr('b'), '1'); + assert.equal($('#spread-leading').attr('c'), '2'); + + assert.equal($('#spread-trailing').length, 1); + assert.equal($('#spread-trailing').attr('a'), '0'); + assert.equal($('#spread-trailing').attr('b'), '1'); + assert.equal($('#spread-trailing').attr('c'), '2'); + }); + + it('Allows spread attributes with TypeScript', async () => { + const html = await fixture.readFile('/spread/index.html'); + const $ = cheerio.load(html); + + assert.equal($('#spread-ts').length, 1); + assert.equal($('#spread-ts').attr('a'), '0'); + assert.equal($('#spread-ts').attr('b'), '1'); + assert.equal($('#spread-ts').attr('c'), '2'); + }); + + it('Allows scoped classes with spread', async () => { + const html = await fixture.readFile('/spread-scope/index.html'); + const $ = cheerio.load(html); + + assert.equal($('#spread-plain').length, 1); + assert.match($('#spread-plain').attr('class'), /astro-.*/); + + assert.equal($('#spread-class').length, 1); + assert.match($('#spread-class').attr('class'), /astro-.*/); + + assert.equal($('#spread-class-list').length, 1); + assert.match($('#spread-class-list').attr('class'), /astro-.*/); + }); + + it('Allows using the Fragment element', async () => { + const html = await fixture.readFile('/fragment/index.html'); + const $ = cheerio.load(html); + assert.equal($('#one').length, 1); + }); + + it('renders components top-down', async () => { + const html = await fixture.readFile('/order/index.html'); + const $ = cheerio.load(html); + assert.equal($('#rendered-order').text(), 'Rendered order: A, B'); + }); + + it('renders markdown in utf-8 by default', async () => { + const html = await fixture.readFile('/chinese-encoding-md/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), '我的第一篇博客文章'); + assert.match(html, / { + const html = await fixture.readFile('/input/index.html'); + const $ = cheerio.load(html); + + assert.equal($('body > :nth-child(1)').prop('outerHTML'), ''); + assert.equal($('body > :nth-child(2)').prop('outerHTML'), ''); + assert.equal($('body > :nth-child(3)').prop('outerHTML'), ''); + assert.equal( + $('body > :nth-child(4)').prop('outerHTML'), + '', + ); + assert.equal($('body > :nth-child(5)').prop('outerHTML'), ''); + }); + + it('Generates pages that end with .mjs', async () => { + const content1 = await fixture.readFile('/get-static-paths-with-mjs/example.mjs'); + assert.ok(content1); + const content2 = await fixture.readFile('/get-static-paths-with-mjs/example.js'); + assert.ok(content2); + }); + + it('allows file:// urls as module specifiers', async () => { + const html = await fixture.readFile('/fileurl/index.html'); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'WORKS'); + }); + + it('Handles importing .astro?raw correctly', async () => { + const html = await fixture.readFile('/import-queries/raw/index.html'); + const $ = cheerio.load(html); + const rawValue = $('.raw-value').text(); + assert.match(rawValue, /

Hello<\/h1>/); + assert.match(rawValue, /