From b81d988cc2a3c31f812d69bc6f743c8ee3a836b5 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 5 Sep 2024 17:12:42 +0800 Subject: [PATCH 1/4] Code change --- packages/astro/src/core/config/schema.ts | 33 +++++++++++++++++-- packages/astro/src/types/public/config.ts | 20 ++++++++--- packages/astro/src/vite-plugin-utils/index.ts | 3 +- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 4c079eae9245..e78a148a0431 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -47,6 +47,12 @@ type RehypePlugin = ComplexifyWithUnion<_RehypePlugin>; type RemarkPlugin = ComplexifyWithUnion<_RemarkPlugin>; type RemarkRehype = ComplexifyWithOmit<_RemarkRehype>; +const suggestedTrailingSlashToBuildFormat = { + always: 'directory', + never: 'file', + ignore: 'preserve', +} as const; + export const ASTRO_CONFIG_DEFAULTS = { root: '.', srcDir: './src', @@ -54,7 +60,7 @@ export const ASTRO_CONFIG_DEFAULTS = { outDir: './dist', cacheDir: './node_modules/.astro', base: '/', - trailingSlash: 'ignore', + trailingSlash: 'always', build: { format: 'directory', client: './dist/client/', @@ -540,6 +546,8 @@ export const AstroConfigSchema = z.object({ export type AstroConfigType = z.infer; export function createRelativeSchema(cmd: string, fileProtocolRoot: string) { + let originalBuildFormat: 'file' | 'directory' | 'preserve' | undefined; + // We need to extend the global schema to add transforms that are relative to root. // This is type checked against the global schema to make sure we still match. const AstroConfigRelativeSchema = AstroConfigSchema.extend({ @@ -566,10 +574,14 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: string) { .transform((val) => resolveDirAsUrl(val, fileProtocolRoot)), build: z .object({ + // NOTE: The `format` default will be set later as it's based on the `trailingSlash` value format: z .union([z.literal('file'), z.literal('directory'), z.literal('preserve')]) .optional() - .default(ASTRO_CONFIG_DEFAULTS.build.format), + .transform((val) => { + originalBuildFormat = val; + return val ?? ASTRO_CONFIG_DEFAULTS.build.format; + }), client: z .string() .optional() @@ -660,6 +672,23 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: string) { config.base = prependForwardSlash(config.base); } + // Handle `build.format` default based on `trailingSlash` config + const suggestedBuildFormat = suggestedTrailingSlashToBuildFormat[config.trailingSlash]; + if (originalBuildFormat === undefined) { + config.build.format = suggestedBuildFormat; + } else if (config.build.format !== suggestedBuildFormat) { + const suggestedTrailingSlash = Object.entries(suggestedTrailingSlashToBuildFormat).find( + ([_, format]) => format === config.build.format, + )?.[0]; + // Use console.warn instead of logger because it's hard to pass the logger here + // eslint-disable-next-line no-console + console.warn( + `The trailingSlash option is set as "${config.trailingSlash}", but build.format is set as "${config.build.format}". ` + + `This will likely cause inconsistencies in relative paths when developing your app. To fix this, remove the build.format ` + + `option so it defaults to "${suggestedBuildFormat}", or set trailingSlash to "${suggestedTrailingSlash}".`, + ); + } + return config; }) .refine((obj) => !obj.outDir.toString().startsWith(obj.publicDir.toString()), { diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index aa1eabd82a65..fb07c2e87efd 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -170,18 +170,27 @@ export interface AstroUserConfig { * @docs * @name trailingSlash * @type {('always' | 'never' | 'ignore')} - * @default `'ignore'` + * @default `'always'` * @see build.format * @description * - * Set the route matching behavior of the dev server. Choose from the following options: + * Set the route matching behavior of the dev server and serving of on-demand routes in production. Choose from the following options: * - `'always'` - Only match URLs that include a trailing slash (ex: "/foo/") * - `'never'` - Never match URLs that include a trailing slash (ex: "/foo") * - `'ignore'` - Match URLs regardless of whether a trailing "/" exists * - * Use this configuration option if your production host has strict handling of how trailing slashes work or do not work. + * For pages that are prerendered (e.g. `src/pages/foo.html`), they will be written as `.html` files like below: + * | `trailingSlash` | `src/pages/about.astro` | `src/pages/about/index.astro` | + * | - | - | - | + * | `'always'` | `dist/about/index.html` | `dist/about/index.html` | + * | `'never'` | `dist/about.html` | `dist/about.html` | + * | `'ignore'` | `dist/about.html` | `dist/about/index.html` | + * + * Using `'ignore'` is not recommended as it allows the same page to exist in two paths and affects SEO. See + * https://www.seroundtable.com/google-trailing-slashes-url-24943.html for more details. + * + * When set to `'always'`, URL paths already contains an extension, e.g. endpoints from `src/pages/api.json.ts`, do not need a trailing slash. * - * You can also set this if you prefer to be more strict yourself, so that URLs with or without trailing slashes won't work during development. * * ```js * { @@ -527,7 +536,8 @@ export interface AstroUserConfig { * @docs * @name build.format * @typeraw {('file' | 'directory' | 'preserve')} - * @default `'directory'` + * @default `'directory'` if `trailingSlash: 'always'`, `'file'` if `trailingSlash: 'never'`, `'preserve'` if `trailingSlash: 'ignore'` + * @deprecated Use the `trailingSlash` option instead. * @description * Control the output file format of each page. This value may be set by an adapter for you. * - `'file'`: Astro will generate an HTML file named for each page route. (e.g. `src/pages/about.astro` and `src/pages/about/index.astro` both build the file `/about.html`) diff --git a/packages/astro/src/vite-plugin-utils/index.ts b/packages/astro/src/vite-plugin-utils/index.ts index 2e7948bdd6ea..2bf398a78249 100644 --- a/packages/astro/src/vite-plugin-utils/index.ts +++ b/packages/astro/src/vite-plugin-utils/index.ts @@ -1,4 +1,5 @@ import { fileURLToPath } from 'node:url'; +import path from 'node:path' import ancestor from 'common-ancestor-path'; import { appendExtension, @@ -19,7 +20,7 @@ export function getFileInfo(id: string, config: AstroConfig) { .replace(/^.*?\/pages\//, sitePathname) .replace(/(?:\/index)?\.(?:md|markdown|mdown|mkdn|mkd|mdwn|astro)$/, '') : undefined; - if (fileUrl && config.trailingSlash === 'always') { + if (fileUrl && config.trailingSlash === 'always' && !path.basename(fileUrl).includes('.')) { fileUrl = appendForwardSlash(fileUrl); } if (fileUrl && config.build.format === 'file') { From 0bac08df3b6b759853d6e8b131dbd93823017a62 Mon Sep 17 00:00:00 2001 From: bluwy Date: Thu, 5 Sep 2024 17:12:52 +0800 Subject: [PATCH 2/4] so many test --- packages/astro/test/actions.test.js | 12 +- packages/astro/test/astro-basic.test.js | 4 +- packages/astro/test/astro-cookies.test.js | 32 ++-- packages/astro/test/astro-envs.test.js | 4 +- .../astro/test/astro-get-static-paths.test.js | 16 +- packages/astro/test/astro-global.test.js | 8 +- packages/astro/test/astro-scripts.test.js | 6 +- packages/astro/test/container.test.js | 6 +- .../content-collection-references.test.js | 2 +- .../test/content-collections-render.test.js | 10 +- .../astro/test/content-collections.test.js | 6 +- .../astro/test/content-layer-render.test.js | 2 +- packages/astro/test/content-layer.test.js | 1 - packages/astro/test/core-image.test.js | 52 +++--- packages/astro/test/dev-routing.test.js | 10 +- .../test/dynamic-endpoint-collision.test.js | 4 +- ...ntal-content-collection-references.test.js | 2 +- .../experimental-content-collections.test.js | 4 +- .../astro-cookies/src/pages/from.astro | 2 +- ...ing-manual-with-default-middleware.test.js | 12 +- .../astro/test/i18n-routing-manual.test.js | 18 +- packages/astro/test/i18n-routing.test.js | 162 +++++++++--------- packages/astro/test/middleware.test.js | 40 ++--- packages/astro/test/preview-routing.test.js | 4 +- packages/astro/test/public-base-404.test.js | 2 +- packages/astro/test/redirects.test.js | 23 +-- .../test/reuse-injected-entrypoint.test.js | 22 +-- packages/astro/test/rewrite.test.js | 66 ++++--- packages/astro/test/routing-priority.test.js | 110 ++++++------ packages/astro/test/set-html.test.js | 4 +- ...special-chars-in-component-imports.test.js | 2 +- packages/astro/test/ssr-api-route.test.js | 6 +- packages/astro/test/ssr-dynamic.test.js | 10 +- packages/astro/test/ssr-env.test.js | 2 +- packages/astro/test/ssr-error-pages.test.js | 59 +------ packages/astro/test/ssr-locals.test.js | 6 +- packages/astro/test/ssr-markdown.test.js | 2 +- packages/astro/test/ssr-params.test.js | 4 +- .../ssr-prerender-get-static-paths.test.js | 32 ++-- packages/astro/test/ssr-prerender.test.js | 2 +- packages/astro/test/ssr-request.test.js | 6 +- packages/astro/test/ssr-response.test.js | 8 +- packages/astro/test/static-build.test.js | 2 +- packages/astro/test/streaming.test.js | 4 +- packages/astro/test/units/dev/dev.test.js | 6 +- .../test/units/routing/endpoints.test.js | 6 +- .../test/units/runtime/endpoints.test.js | 2 +- .../vite-plugin-astro-server/request.test.js | 8 +- .../vite-plugin-astro-server/response.test.js | 2 +- packages/astro/test/view-transitions.test.js | 4 +- 50 files changed, 383 insertions(+), 436 deletions(-) diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js index 334e07a173e2..5446226cc3d0 100644 --- a/packages/astro/test/actions.test.js +++ b/packages/astro/test/actions.test.js @@ -39,7 +39,7 @@ describe('Astro Actions', () => { }), }), ); - const res = await fixture.fetch('/subscribe-prerendered', { + const res = await fixture.fetch('/subscribe-prerendered/', { headers: { Cookie: cookie.toString(), }, @@ -197,7 +197,7 @@ describe('Astro Actions', () => { }); it('Response middleware fallback', async () => { - const req = new Request('http://example.com/user?_astroAction=getUser', { + const req = new Request('http://example.com/user/?_astroAction=getUser', { method: 'POST', body: new FormData(), headers: { @@ -213,11 +213,11 @@ describe('Astro Actions', () => { }); it('Respects custom errors', async () => { - const req = new Request('http://example.com/user-or-throw?_astroAction=getUserOrThrow', { + const req = new Request('http://example.com/user-or-throw/?_astroAction=getUserOrThrow', { method: 'POST', body: new FormData(), headers: { - Referer: 'http://example.com/user-or-throw', + Referer: 'http://example.com/user-or-throw/', }, }); const res = await followExpectedRedirect(req, app); @@ -230,7 +230,7 @@ describe('Astro Actions', () => { }); it('Ignores `_astroAction` name for GET requests', async () => { - const req = new Request('http://example.com/user-or-throw?_astroAction=getUserOrThrow', { + const req = new Request('http://example.com/user-or-throw/?_astroAction=getUserOrThrow', { method: 'GET', }); const res = await app.render(req); @@ -338,7 +338,7 @@ describe('Astro Actions', () => { }); it('Is callable from the server with rewrite', async () => { - const req = new Request('http://example.com/rewrite'); + const req = new Request('http://example.com/rewrite/'); const res = await app.render(req); assert.equal(res.ok, true); diff --git a/packages/astro/test/astro-basic.test.js b/packages/astro/test/astro-basic.test.js index 0fc87dd6068e..c1258ed5590b 100644 --- a/packages/astro/test/astro-basic.test.js +++ b/packages/astro/test/astro-basic.test.js @@ -202,7 +202,7 @@ describe('Astro basic development', () => { }); it('Renders markdown in utf-8 by default', async () => { - const res = await fixture.fetch('/chinese-encoding-md'); + const res = await fixture.fetch('/chinese-encoding-md/'); assert.equal(res.status, 200); const html = await res.text(); const $ = cheerio.load(html); @@ -214,7 +214,7 @@ describe('Astro basic development', () => { }); it('Renders MDX in utf-8 by default', async () => { - const res = await fixture.fetch('/chinese-encoding-mdx'); + const res = await fixture.fetch('/chinese-encoding-mdx/'); assert.equal(res.status, 200); const html = await res.text(); const $ = cheerio.load(html); diff --git a/packages/astro/test/astro-cookies.test.js b/packages/astro/test/astro-cookies.test.js index 482474b54be2..18d6075ae7a1 100644 --- a/packages/astro/test/astro-cookies.test.js +++ b/packages/astro/test/astro-cookies.test.js @@ -29,7 +29,7 @@ describe('Astro.cookies', () => { }); it('is able to get cookies from the request', async () => { - const response = await fixture.fetch('/get-json', { + const response = await fixture.fetch('/get-json/', { headers: { cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`, }, @@ -42,7 +42,7 @@ describe('Astro.cookies', () => { }); it('can set the cookie value', async () => { - const response = await fixture.fetch('/set-value', { + const response = await fixture.fetch('/set-value/', { method: 'POST', }); assert.equal(response.status, 200); @@ -54,26 +54,26 @@ describe('Astro.cookies', () => { }); it('can set cookies in a rewritten page request', async () => { - const response = await fixture.fetch('/from'); + const response = await fixture.fetch('/from/'); assert.equal(response.status, 200); assert.match(response.headers.get('set-cookie'), /my_cookie=value/); }); it('overwrites cookie values set in the source page with values from the target page', async () => { - const response = await fixture.fetch('/from'); + const response = await fixture.fetch('/from/'); assert.equal(response.status, 200); assert.match(response.headers.get('set-cookie'), /another=set-in-target/); }); it('allows cookies to be set in the source page', async () => { - const response = await fixture.fetch('/from'); + const response = await fixture.fetch('/from/'); assert.equal(response.status, 200); assert.match(response.headers.get('set-cookie'), /set-in-from=yes/); }); it('can set cookies in a rewritten endpoint request', async () => { - const response = await fixture.fetch('/from-endpoint'); + const response = await fixture.fetch('/from-endpoint/'); assert.equal(response.status, 200); assert.match(response.headers.get('set-cookie'), /test=value/); }); @@ -93,7 +93,7 @@ describe('Astro.cookies', () => { } it('is able to get cookies from the request', async () => { - const response = await fetchResponse('/get-json', { + const response = await fetchResponse('/get-json/', { headers: { cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`, }, @@ -106,7 +106,7 @@ describe('Astro.cookies', () => { }); it('can set the cookie value', async () => { - const response = await fetchResponse('/set-value', { + const response = await fetchResponse('/set-value/', { method: 'POST', }); assert.equal(response.status, 200); @@ -116,7 +116,7 @@ describe('Astro.cookies', () => { }); it('app.render can include the cookie in the Set-Cookie header', async () => { - const request = new Request('http://example.com/set-value', { + const request = new Request('http://example.com/set-value/', { method: 'POST', }); const response = await app.render(request, { addCookieHeader: true }); @@ -127,7 +127,7 @@ describe('Astro.cookies', () => { }); it('app.render can exclude the cookie from the Set-Cookie header', async () => { - const request = new Request('http://example.com/set-value', { + const request = new Request('http://example.com/set-value/', { method: 'POST', }); const response = await app.render(request, { addCookieHeader: false }); @@ -136,7 +136,7 @@ describe('Astro.cookies', () => { }); it('Early returning a Response still includes set headers', async () => { - const response = await fetchResponse('/early-return', { + const response = await fetchResponse('/early-return/', { headers: { cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`, }, @@ -151,7 +151,7 @@ describe('Astro.cookies', () => { }); it('API route can get and set cookies', async () => { - const response = await fetchResponse('/set-prefs', { + const response = await fetchResponse('/set-prefs/', { method: 'POST', headers: { cookie: `prefs=${encodeURIComponent(JSON.stringify({ mode: 'light' }))}`, @@ -167,7 +167,7 @@ describe('Astro.cookies', () => { }); it('can set cookies in a rewritten page request', async () => { - const request = new Request('http://example.com/from'); + const request = new Request('http://example.com/from/'); const response = await app.render(request, { addCookieHeader: true }); assert.equal(response.status, 200); @@ -175,21 +175,21 @@ describe('Astro.cookies', () => { }); it('overwrites cookie values set in the source page with values from the target page', async () => { - const request = new Request('http://example.com/from'); + const request = new Request('http://example.com/from/'); const response = await app.render(request, { addCookieHeader: true }); assert.equal(response.status, 200); assert.match(response.headers.get('Set-Cookie'), /another=set-in-target/); }); it('allows cookies to be set in the source page', async () => { - const request = new Request('http://example.com/from'); + const request = new Request('http://example.com/from/'); const response = await app.render(request, { addCookieHeader: true }); assert.equal(response.status, 200); assert.match(response.headers.get('Set-Cookie'), /set-in-from=yes/); }); it('can set cookies in a rewritten endpoint request', async () => { - const request = new Request('http://example.com/from-endpoint'); + const request = new Request('http://example.com/from-endpoint/'); const response = await app.render(request, { addCookieHeader: true }); assert.equal(response.status, 200); assert.match(response.headers.get('Set-Cookie'), /test=value/); diff --git a/packages/astro/test/astro-envs.test.js b/packages/astro/test/astro-envs.test.js index 94844fd54134..f3c6592c3326 100644 --- a/packages/astro/test/astro-envs.test.js +++ b/packages/astro/test/astro-envs.test.js @@ -110,7 +110,7 @@ describe('Environment Variables', () => { assert.equal(res.status, 200); let indexHtml = await res.text(); let $ = cheerio.load(indexHtml); - assert.equal($('#base-url').text(), '/blog'); + assert.equal($('#base-url').text(), '/blog/'); }); it('does render destructured builtin SITE env', async () => { @@ -118,7 +118,7 @@ describe('Environment Variables', () => { assert.equal(res.status, 200); let indexHtml = await res.text(); let $ = cheerio.load(indexHtml); - assert.equal($('#base-url').text(), '/blog'); + assert.equal($('#base-url').text(), '/blog/'); }); }); }); diff --git a/packages/astro/test/astro-get-static-paths.test.js b/packages/astro/test/astro-get-static-paths.test.js index c451d577fdbf..170391ba8a09 100644 --- a/packages/astro/test/astro-get-static-paths.test.js +++ b/packages/astro/test/astro-get-static-paths.test.js @@ -69,22 +69,22 @@ describe('getStaticPaths - dev calls', () => { describe('404 behavior', () => { it('resolves 200 on matching static path - named params', async () => { - const res = await fixture.fetch('/pizza/provolone-sausage'); + const res = await fixture.fetch('/pizza/provolone-sausage/'); assert.equal(res.status, 200); }); it('resolves 404 on pattern match without static path - named params', async () => { - const res = await fixture.fetch('/pizza/provolone-pineapple'); + const res = await fixture.fetch('/pizza/provolone-pineapple/'); assert.equal(res.status, 404); }); it('resolves 200 on matching static path - rest params', async () => { - const res = await fixture.fetch('/pizza/grimaldis/new-york'); + const res = await fixture.fetch('/pizza/grimaldis/new-york/'); assert.equal(res.status, 200); }); it('resolves 404 on pattern match without static path - rest params', async () => { - const res = await fixture.fetch('/pizza/pizza-hut'); + const res = await fixture.fetch('/pizza/pizza-hut/'); assert.equal(res.status, 404); }); }); @@ -92,21 +92,21 @@ describe('getStaticPaths - dev calls', () => { describe('route params type validation', () => { it('resolves 200 on matching static path - string params', async () => { // route provided with { params: { year: "2022", slug: "post-2" }} - const res = await fixture.fetch('/blog/2022/post-1'); + const res = await fixture.fetch('/blog/2022/post-1/'); assert.equal(res.status, 200); }); it('resolves 200 on matching static path - numeric params', async () => { // route provided with { params: { year: 2022, slug: "post-2" }} - const res = await fixture.fetch('/blog/2022/post-2'); + const res = await fixture.fetch('/blog/2022/post-2/'); assert.equal(res.status, 200); }); }); it('resolves 200 on matching static paths', async () => { - // routes params provided for pages /posts/1, /posts/2, and /posts/3 + // routes params provided for pages /posts/1/, /posts/2/, and /posts/3/ for (const page of [1, 2, 3]) { - let res = await fixture.fetch(`/posts/${page}`); + let res = await fixture.fetch(`/posts/${page}/`); assert.equal(res.status, 200); const html = await res.text(); diff --git a/packages/astro/test/astro-global.test.js b/packages/astro/test/astro-global.test.js index 353de4a1f762..efc31182761a 100644 --- a/packages/astro/test/astro-global.test.js +++ b/packages/astro/test/astro-global.test.js @@ -71,10 +71,10 @@ describe('Astro Global', () => { const html = await fixture.readFile('/index.html'); const $ = cheerio.load(html); - assert.equal($('#pathname').text(), '/blog'); + assert.equal($('#pathname').text(), '/blog/'); assert.equal($('#searchparams').text(), '{}'); - assert.equal($('#child-pathname').text(), '/blog'); - assert.equal($('#nested-child-pathname').text(), '/blog'); + assert.equal($('#child-pathname').text(), '/blog/'); + assert.equal($('#nested-child-pathname').text(), '/blog/'); }); it('Astro.site', async () => { @@ -86,7 +86,7 @@ describe('Astro Global', () => { it('Astro.glob() correctly returns an array of all posts', async () => { const html = await fixture.readFile('/posts/1/index.html'); const $ = cheerio.load(html); - assert.equal($('.post-url').attr('href'), '/blog/post/post-2'); + assert.equal($('.post-url').attr('href'), '/blog/post/post-2/'); }); it('Astro.glob() correctly returns meta info for MD and Astro files', async () => { diff --git a/packages/astro/test/astro-scripts.test.js b/packages/astro/test/astro-scripts.test.js index 38b7bd4ff01d..5983f56fec7b 100644 --- a/packages/astro/test/astro-scripts.test.js +++ b/packages/astro/test/astro-scripts.test.js @@ -132,7 +132,7 @@ describe('Scripts', () => { }); it('Scripts added via Astro.glob are hoisted', async () => { - let res = await fixture.fetch('/glob'); + let res = await fixture.fetch('/glob/'); let html = await res.text(); let $ = cheerio.load(html); @@ -149,7 +149,7 @@ describe('Scripts', () => { }); it('Using injectScript does not interfere', async () => { - let res = await fixture.fetch('/inline-in-page'); + let res = await fixture.fetch('/inline-in-page/'); let html = await res.text(); let $ = cheerio.load(html); let found = 0; @@ -163,7 +163,7 @@ describe('Scripts', () => { }); it('Injected scripts are injected to injected routes', async () => { - let res = await fixture.fetch('/injected-route'); + let res = await fixture.fetch('/injected-route/'); let html = await res.text(); let $ = cheerio.load(html); let found = 0; diff --git a/packages/astro/test/container.test.js b/packages/astro/test/container.test.js index 7d8b61d9c658..c422bed134d2 100644 --- a/packages/astro/test/container.test.js +++ b/packages/astro/test/container.test.js @@ -247,7 +247,7 @@ describe('Container with renderers', () => { }); it('the endpoint should return the HTML of the React component', async () => { - const request = new Request('https://example.com/react'); + const request = new Request('https://example.com/react/'); const response = await app.render(request); const html = await response.text(); @@ -255,7 +255,7 @@ describe('Container with renderers', () => { }); it('the endpoint should return the HTML of the Vue component', async () => { - const request = new Request('https://example.com/vue'); + const request = new Request('https://example.com/vue/'); const response = await app.render(request); const html = await response.text(); @@ -263,7 +263,7 @@ describe('Container with renderers', () => { }); it('Should render a component with directives', async () => { - const request = new Request('https://example.com/button-directive'); + const request = new Request('https://example.com/button-directive/'); const response = await app.render(request); const html = await response.text(); diff --git a/packages/astro/test/content-collection-references.test.js b/packages/astro/test/content-collection-references.test.js index 7119e0f0460c..5a4e1597df03 100644 --- a/packages/astro/test/content-collection-references.test.js +++ b/packages/astro/test/content-collection-references.test.js @@ -119,7 +119,7 @@ describe('Content Collections - references', () => { const html = await fixture.readFile('/welcome/index.html'); $ = cheerio.load(html); } else if (mode === 'dev') { - const htmlResponse = await fixture.fetch('/welcome'); + const htmlResponse = await fixture.fetch('/welcome/'); const html = await htmlResponse.text(); $ = cheerio.load(html); } diff --git a/packages/astro/test/content-collections-render.test.js b/packages/astro/test/content-collections-render.test.js index 31ed04a15ae5..5bb6fc529285 100644 --- a/packages/astro/test/content-collections-render.test.js +++ b/packages/astro/test/content-collections-render.test.js @@ -101,7 +101,7 @@ describe('Content Collections - render()', () => { it('Includes CSS for rendered entry', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/launch-week'); + const request = new Request('http://example.com/launch-week/'); const response = await app.render(request); const html = await response.text(); const $ = cheerio.load(html); @@ -192,7 +192,7 @@ describe('Content Collections - render()', () => { }); it('Includes CSS for rendered entry', async () => { - const response = await fixture.fetch('/launch-week', { method: 'GET' }); + const response = await fixture.fetch('/launch-week/', { method: 'GET' }); assert.equal(response.status, 200); const html = await response.text(); @@ -207,7 +207,7 @@ describe('Content Collections - render()', () => { }); it('Includes component scripts for rendered entry', async () => { - const response = await fixture.fetch('/launch-week-component-scripts', { method: 'GET' }); + const response = await fixture.fetch('/launch-week-component-scripts/', { method: 'GET' }); assert.equal(response.status, 200); const html = await response.text(); @@ -221,7 +221,7 @@ describe('Content Collections - render()', () => { }); it('Applies MDX components export', async () => { - const response = await fixture.fetch('/launch-week-components-export', { method: 'GET' }); + const response = await fixture.fetch('/launch-week-components-export/', { method: 'GET' }); assert.equal(response.status, 200); const html = await response.text(); @@ -233,7 +233,7 @@ describe('Content Collections - render()', () => { }); it('Supports layout prop with recursive getCollection() call', async () => { - const response = await fixture.fetch('/with-layout-prop', { method: 'GET' }); + const response = await fixture.fetch('/with-layout-prop/', { method: 'GET' }); assert.equal(response.status, 200); const html = await response.text(); diff --git a/packages/astro/test/content-collections.test.js b/packages/astro/test/content-collections.test.js index 5bb3736b75d5..916cbdf41ded 100644 --- a/packages/astro/test/content-collections.test.js +++ b/packages/astro/test/content-collections.test.js @@ -336,7 +336,7 @@ describe('Content Collections', () => { it('Responds 200 for expected pages', async () => { for (const slug in blogSlugToContents) { - const request = new Request('http://example.com/posts/' + slug); + const request = new Request('http://example.com/posts/' + slug + '/'); const response = await app.render(request); assert.equal(response.status, 200); } @@ -344,7 +344,7 @@ describe('Content Collections', () => { it('Renders titles', async () => { for (const slug in blogSlugToContents) { - const request = new Request('http://example.com/posts/' + slug); + const request = new Request('http://example.com/posts/' + slug + '/'); const response = await app.render(request); const body = await response.text(); const $ = cheerio.load(body); @@ -354,7 +354,7 @@ describe('Content Collections', () => { it('Renders content', async () => { for (const slug in blogSlugToContents) { - const request = new Request('http://example.com/posts/' + slug); + const request = new Request('http://example.com/posts/' + slug + '/'); const response = await app.render(request); const body = await response.text(); const $ = cheerio.load(body); diff --git a/packages/astro/test/content-layer-render.test.js b/packages/astro/test/content-layer-render.test.js index fa743e719ca4..4ccf48e65e4b 100644 --- a/packages/astro/test/content-layer-render.test.js +++ b/packages/astro/test/content-layer-render.test.js @@ -19,7 +19,7 @@ describe('Content Layer MDX rendering dev', () => { }); it('Render an MDX file', async () => { - const html = await fixture.fetch('/reptiles/iguana').then((r) => r.text()); + const html = await fixture.fetch('/reptiles/iguana/').then((r) => r.text()); assert.match(html, /Iguana/); assert.match(html, /This is a rendered entry/); diff --git a/packages/astro/test/content-layer.test.js b/packages/astro/test/content-layer.test.js index d61d3045046e..74e72797793b 100644 --- a/packages/astro/test/content-layer.test.js +++ b/packages/astro/test/content-layer.test.js @@ -140,7 +140,6 @@ describe('Content Layer', () => { }); it('handles remote images in custom loaders', async () => { - console.log(json.images[1].data.image); assert.ok(json.images[1].data.image.startsWith('https://')); }); diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js index 9c62a679ef80..8ac2a26ac18a 100644 --- a/packages/astro/test/core-image.test.js +++ b/packages/astro/test/core-image.test.js @@ -111,7 +111,7 @@ describe('astro:image', () => { }); it('properly skip processing SVGs, but does not error', async () => { - let res = await fixture.fetch('/svgSupport'); + let res = await fixture.fetch('/svgSupport/'); let html = await res.text(); $ = cheerio.load(html); @@ -125,7 +125,7 @@ describe('astro:image', () => { it("errors when an ESM imported image's src is passed to Image/getImage instead of the full import", async () => { logs.length = 0; - let res = await fixture.fetch('/error-image-src-passed'); + let res = await fixture.fetch('/error-image-src-passed/'); await res.text(); assert.equal(logs.length, 1); @@ -133,7 +133,7 @@ describe('astro:image', () => { }); it('supports images from outside the project', async () => { - let res = await fixture.fetch('/outsideProject'); + let res = await fixture.fetch('/outsideProject/'); let html = await res.text(); $ = cheerio.load(html); @@ -151,7 +151,7 @@ describe('astro:image', () => { }); it('supports inlined imports', async () => { - let res = await fixture.fetch('/inlineImport'); + let res = await fixture.fetch('/inlineImport/'); let html = await res.text(); $ = cheerio.load(html); @@ -164,7 +164,7 @@ describe('astro:image', () => { }); it('supports uppercased imports', async () => { - let res = await fixture.fetch('/uppercase'); + let res = await fixture.fetch('/uppercase/'); let html = await res.text(); $ = cheerio.load(html); @@ -179,7 +179,7 @@ describe('astro:image', () => { }); it('supports avif', async () => { - let res = await fixture.fetch('/avif'); + let res = await fixture.fetch('/avif/'); let html = await res.text(); $ = cheerio.load(html); @@ -193,7 +193,7 @@ describe('astro:image', () => { }); it('has a working Picture component', async () => { - let res = await fixture.fetch('/picturecomponent'); + let res = await fixture.fetch('/picturecomponent/'); let html = await res.text(); $ = cheerio.load(html); @@ -267,7 +267,7 @@ describe('astro:image', () => { }); it('Picture component scope styles work', async () => { - let res = await fixture.fetch('/picturecomponent'); + let res = await fixture.fetch('/picturecomponent/'); let html = await res.text(); $ = cheerio.load(html); @@ -280,7 +280,7 @@ describe('astro:image', () => { }); it('properly deduplicate srcset images', async () => { - let res = await fixture.fetch('/srcset'); + let res = await fixture.fetch('/srcset/'); let html = await res.text(); $ = cheerio.load(html); @@ -346,7 +346,7 @@ describe('astro:image', () => { */ let $; before(async () => { - let res = await fixture.fetch('/vite'); + let res = await fixture.fetch('/vite/'); let html = await res.text(); $ = cheerio.load(html); }); @@ -422,7 +422,7 @@ describe('astro:image', () => { it('error if no width and height', async () => { logs.length = 0; - let res = await fixture.fetch('/remote-error-no-dimensions'); + let res = await fixture.fetch('/remote-error-no-dimensions/'); await res.text(); assert.equal(logs.length, 1); @@ -431,7 +431,7 @@ describe('astro:image', () => { it('error if no height', async () => { logs.length = 0; - let res = await fixture.fetch('/remote-error-no-height'); + let res = await fixture.fetch('/remote-error-no-height/'); await res.text(); assert.equal(logs.length, 1); @@ -439,7 +439,7 @@ describe('astro:image', () => { }); it('supports aliases', async () => { - let res = await fixture.fetch('/alias'); + let res = await fixture.fetch('/alias/'); let html = await res.text(); let $ = cheerio.load(html); @@ -452,7 +452,7 @@ describe('astro:image', () => { describe('markdown', () => { let $; before(async () => { - let res = await fixture.fetch('/post'); + let res = await fixture.fetch('/post/'); let html = await res.text(); $ = cheerio.load(html); }); @@ -475,7 +475,7 @@ describe('astro:image', () => { }); it('Supports aliased paths', async () => { - let res = await fixture.fetch('/aliasMarkdown'); + let res = await fixture.fetch('/aliasMarkdown/'); let html = await res.text(); $ = cheerio.load(html); @@ -512,7 +512,7 @@ describe('astro:image', () => { describe('getImage', () => { let $; before(async () => { - let res = await fixture.fetch('/get-image'); + let res = await fixture.fetch('/get-image/'); let html = await res.text(); $ = cheerio.load(html); }); @@ -532,7 +532,7 @@ describe('astro:image', () => { describe('content collections', () => { let $; before(async () => { - let res = await fixture.fetch('/blog/one'); + let res = await fixture.fetch('/blog/one/'); let html = await res.text(); $ = cheerio.load(html); }); @@ -595,7 +595,7 @@ describe('astro:image', () => { /** @type {ReturnType} */ let $; before(async () => { - let res = await fixture.fetch('/regular-img'); + let res = await fixture.fetch('/regular-img/'); let html = await res.text(); $ = cheerio.load(html); }); @@ -610,7 +610,7 @@ describe('astro:image', () => { }); describe('custom service', () => { - it('custom service implements getHTMLAttributes', async () => { + it('custom service implements getHTMLAttributes/', async () => { const response = await fixture.fetch('/'); const html = await response.text(); @@ -619,7 +619,7 @@ describe('astro:image', () => { }); it('custom service works in Markdown', async () => { - const response = await fixture.fetch('/post'); + const response = await fixture.fetch('/post/'); const html = await response.text(); const $ = cheerio.load(html); @@ -712,7 +712,7 @@ describe('astro:image', () => { it("properly error when getImage's first parameter isn't filled", async () => { logs.length = 0; - let res = await fixture.fetch('/get-image-empty'); + let res = await fixture.fetch('/get-image-empty/'); await res.text(); assert.equal(logs.length >= 1, true); @@ -721,7 +721,7 @@ describe('astro:image', () => { it('properly error when src is undefined', async () => { logs.length = 0; - let res = await fixture.fetch('/get-image-undefined'); + let res = await fixture.fetch('/get-image-undefined/'); await res.text(); assert.equal(logs.length >= 1, true); @@ -730,7 +730,7 @@ describe('astro:image', () => { it('errors when an ESM imported image is passed directly to getImage', async () => { logs.length = 0; - let res = await fixture.fetch('/get-image-import-passed'); + let res = await fixture.fetch('/get-image-import-passed/'); await res.text(); assert.equal(logs.length >= 1, true); assert.equal( @@ -741,7 +741,7 @@ describe('astro:image', () => { it('properly error image in Markdown frontmatter is not found', async () => { logs.length = 0; - let res = await fixture.fetch('/blog/one'); + let res = await fixture.fetch('/blog/one/'); await res.text(); assert.equal(logs.length, 1); @@ -750,7 +750,7 @@ describe('astro:image', () => { it('properly error image in Markdown content is not found', async () => { logs.length = 0; - let res = await fixture.fetch('/post'); + let res = await fixture.fetch('/post/'); await res.text(); assert.equal(logs.length, 1); assert.equal(logs[0].message.includes('Could not find requested image'), true); @@ -1127,7 +1127,7 @@ describe('astro:image', () => { }); it('does not interfere with query params', async () => { - let res = await fixture.fetch('/api?src=image.png'); + let res = await fixture.fetch('/api/?src=image.png'); const html = await res.text(); assert.equal(html, 'An image: "image.png"'); }); diff --git a/packages/astro/test/dev-routing.test.js b/packages/astro/test/dev-routing.test.js index c6a19fc4e440..4e11ec165962 100644 --- a/packages/astro/test/dev-routing.test.js +++ b/packages/astro/test/dev-routing.test.js @@ -258,7 +258,7 @@ describe('Development Routing', () => { }); it('error responses are served untouched', async () => { - const response = await fixture.fetch('/not-ok'); + const response = await fixture.fetch('/not-ok/'); assert.equal(response.status, 404); assert.equal(response.headers.get('Content-Type'), 'text/plain;charset=UTF-8'); const body = await response.text(); @@ -286,7 +286,7 @@ describe('Development Routing', () => { }); it('correct encoding when loading /images/hex.ts', async () => { - const response = await fixture.fetch('/images/hex'); + const response = await fixture.fetch('/images/hex/'); const body = await response.arrayBuffer(); const hex = Buffer.from(body).toString('hex', 0, 4); @@ -295,7 +295,7 @@ describe('Development Routing', () => { }); }); - describe('file format routing', () => { + describe('trailingSlash: never', () => { /** @type {import('./test-utils').Fixture} */ let fixture; /** @type {import('./test-utils').DevServer} */ @@ -303,9 +303,7 @@ describe('Development Routing', () => { before(async () => { fixture = await loadFixture({ - build: { - format: 'file', - }, + trailingSlash: 'never', root: './fixtures/without-site-config/', site: 'http://example.com/', }); diff --git a/packages/astro/test/dynamic-endpoint-collision.test.js b/packages/astro/test/dynamic-endpoint-collision.test.js index b1aa42f9fdb8..d9d509de23b4 100644 --- a/packages/astro/test/dynamic-endpoint-collision.test.js +++ b/packages/astro/test/dynamic-endpoint-collision.test.js @@ -40,13 +40,13 @@ describe('Dynamic endpoint collision', () => { }); it('throw error when dynamic endpoint has path collision', async () => { - const html = await fixture.fetch('/api/catch').then((res) => res.text()); + const html = await fixture.fetch('/api/catch/').then((res) => res.text()); const $ = cheerioLoad(html); assert.equal($('title').text(), 'PrerenderDynamicEndpointPathCollide'); }); it("don't throw error when dynamic endpoint doesn't load the colliding path", async () => { - const res = await fixture.fetch('/api/catch/one').then((r) => r.text()); + const res = await fixture.fetch('/api/catch/one/').then((r) => r.text()); assert.equal(res, '{"slug":"one"}'); }); }); diff --git a/packages/astro/test/experimental-content-collection-references.test.js b/packages/astro/test/experimental-content-collection-references.test.js index ab458bbd8d7b..038f0549a2a8 100644 --- a/packages/astro/test/experimental-content-collection-references.test.js +++ b/packages/astro/test/experimental-content-collection-references.test.js @@ -124,7 +124,7 @@ describe('Experimental Content Collections cache - references', () => { const html = await fixture.readFile('/welcome/index.html'); $ = cheerio.load(html); } else if (mode === 'dev') { - const htmlResponse = await fixture.fetch('/welcome'); + const htmlResponse = await fixture.fetch('/welcome/'); const html = await htmlResponse.text(); $ = cheerio.load(html); } diff --git a/packages/astro/test/experimental-content-collections.test.js b/packages/astro/test/experimental-content-collections.test.js index b2b2832ce62c..32935215c30e 100644 --- a/packages/astro/test/experimental-content-collections.test.js +++ b/packages/astro/test/experimental-content-collections.test.js @@ -335,7 +335,7 @@ describe('Experimental Content Collections cache', () => { it('Responds 200 for expected pages', async () => { for (const slug in blogSlugToContents) { - const request = new Request('http://example.com/posts/' + slug); + const request = new Request('http://example.com/posts/' + slug + '/'); const response = await app.render(request); assert.equal(response.status, 200); } @@ -343,7 +343,7 @@ describe('Experimental Content Collections cache', () => { it('Renders titles', async () => { for (const slug in blogSlugToContents) { - const request = new Request('http://example.com/posts/' + slug); + const request = new Request('http://example.com/posts/' + slug + '/'); const response = await app.render(request); const body = await response.text(); const $ = cheerio.load(body); diff --git a/packages/astro/test/fixtures/astro-cookies/src/pages/from.astro b/packages/astro/test/fixtures/astro-cookies/src/pages/from.astro index 893858a2838d..32dd9757fd67 100644 --- a/packages/astro/test/fixtures/astro-cookies/src/pages/from.astro +++ b/packages/astro/test/fixtures/astro-cookies/src/pages/from.astro @@ -1,5 +1,5 @@ --- Astro.cookies.set('another','set-in-from'); Astro.cookies.set('set-in-from','yes'); -return Astro.rewrite('/rewrite-target'); +return Astro.rewrite('/rewrite-target/'); --- diff --git a/packages/astro/test/i18n-routing-manual-with-default-middleware.test.js b/packages/astro/test/i18n-routing-manual-with-default-middleware.test.js index af900a43b589..89a704129f21 100644 --- a/packages/astro/test/i18n-routing-manual-with-default-middleware.test.js +++ b/packages/astro/test/i18n-routing-manual-with-default-middleware.test.js @@ -23,21 +23,21 @@ describe('Dev server manual routing', () => { }); it('should return a 404', async () => { - const response = await fixture.fetch('/blog'); + const response = await fixture.fetch('/blog/'); const text = await response.text(); assert.equal(response.status, 404); assert.match(text, /Blog should not render/); }); it('should return a 200 because the custom middleware allows it', async () => { - const response = await fixture.fetch('/about'); + const response = await fixture.fetch('/about/'); assert.equal(response.status, 200); const text = await response.text(); assert.equal(text.includes('ABOUT ME'), true); }); it('should correctly print the relative locale url', async () => { - const response = await fixture.fetch('/en/start'); + const response = await fixture.fetch('/en/start/'); assert.equal(response.status, 200); const html = await response.text(); const $ = cheerio.load(html); @@ -94,7 +94,7 @@ describe('SSR manual routing', () => { }); it('should return a 404', async () => { - let request = new Request('http://example.com/blog'); + let request = new Request('http://example.com/blog/'); let response = await app.render(request); assert.equal(response.status, 404); const text = await response.text(); @@ -102,7 +102,7 @@ describe('SSR manual routing', () => { }); it('should return a 200 because the custom middleware allows it', async () => { - let request = new Request('http://example.com/about'); + let request = new Request('http://example.com/about/'); let response = await app.render(request); assert.equal(response.status, 200); const text = await response.text(); @@ -110,7 +110,7 @@ describe('SSR manual routing', () => { }); it('should correctly print the relative locale url', async () => { - let request = new Request('http://example.com/en/start'); + let request = new Request('http://example.com/en/start/'); let response = await app.render(request); assert.equal(response.status, 200); const html = await response.text(); diff --git a/packages/astro/test/i18n-routing-manual.test.js b/packages/astro/test/i18n-routing-manual.test.js index d0de75fe8dd0..889787ca1cb4 100644 --- a/packages/astro/test/i18n-routing-manual.test.js +++ b/packages/astro/test/i18n-routing-manual.test.js @@ -30,31 +30,31 @@ describe('Dev server manual routing', () => { }); it('should render a route that is not related to the i18n routing', async () => { - const response = await fixture.fetch('/help'); + const response = await fixture.fetch('/help/'); assert.equal(response.status, 200); const text = await response.text(); assert.equal(text.includes('Outside route'), true); }); it('should render a i18n route', async () => { - let response = await fixture.fetch('/en/blog'); + let response = await fixture.fetch('/en/blog/'); assert.equal(response.status, 200); let text = await response.text(); assert.equal(text.includes('Blog start'), true); - response = await fixture.fetch('/pt/start'); + response = await fixture.fetch('/pt/start/'); assert.equal(response.status, 200); text = await response.text(); assert.equal(text.includes('Oi'), true); - response = await fixture.fetch('/spanish'); + response = await fixture.fetch('/spanish/'); assert.equal(response.status, 200); text = await response.text(); assert.equal(text.includes('Hola.'), true); }); it('should not redirect prerendered 404 routes in dev', async () => { - const response = await fixture.fetch('/redirect-me'); + const response = await fixture.fetch('/redirect-me/'); assert.equal(response.status, 404); }); }); @@ -121,7 +121,7 @@ describe('SSR manual routing', () => { }); it('should render a route that is not related to the i18n routing', async () => { - let request = new Request('http://example.com/help'); + let request = new Request('http://example.com/help/'); let response = await app.render(request); assert.equal(response.status, 200); const text = await response.text(); @@ -129,19 +129,19 @@ describe('SSR manual routing', () => { }); it('should render a i18n route', async () => { - let request = new Request('http://example.com/en/blog'); + let request = new Request('http://example.com/en/blog/'); let response = await app.render(request); assert.equal(response.status, 200); let text = await response.text(); assert.equal(text.includes('Blog start'), true); - request = new Request('http://example.com/pt/start'); + request = new Request('http://example.com/pt/start/'); response = await app.render(request); assert.equal(response.status, 200); text = await response.text(); assert.equal(text.includes('Oi'), true); - request = new Request('http://example.com/spanish'); + request = new Request('http://example.com/spanish/'); response = await app.render(request); assert.equal(response.status, 200); text = await response.text(); diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js index ddb31762f490..3138daafd72d 100644 --- a/packages/astro/test/i18n-routing.test.js +++ b/packages/astro/test/i18n-routing.test.js @@ -22,7 +22,7 @@ describe('astro:i18n virtual module', () => { }); it('correctly imports the functions', async () => { - const response = await fixture.fetch('/virtual-module'); + const response = await fixture.fetch('/virtual-module/'); assert.equal(response.status, 200); const text = await response.text(); assert.equal(text.includes("Virtual module doesn't break"), true); @@ -102,7 +102,7 @@ describe('[DEV] i18n routing', () => { }); it('should render the en locale', async () => { - const response = await fixture.fetch('/en/start'); + const response = await fixture.fetch('/en/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); @@ -112,7 +112,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly', async () => { - const response = await fixture.fetch('/pt/start'); + const response = await fixture.fetch('/pt/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Oi essa e start'), true); @@ -122,7 +122,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly when using path+codes', async () => { - const response = await fixture.fetch('/spanish/start'); + const response = await fixture.fetch('/spanish/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Espanol'), true); @@ -132,12 +132,12 @@ describe('[DEV] i18n routing', () => { }); it("should NOT render the default locale if there isn't a fallback and the route is missing", async () => { - const response = await fixture.fetch('/it/start'); + const response = await fixture.fetch('/it/start/'); assert.equal(response.status, 404); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - const response = await fixture.fetch('/fr/start'); + const response = await fixture.fetch('/fr/start/'); assert.equal(response.status, 404); }); }); @@ -160,7 +160,7 @@ describe('[DEV] i18n routing', () => { }); it('should render the en locale', async () => { - const response = await fixture.fetch('/new-site/en/start'); + const response = await fixture.fetch('/new-site/en/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Hello'), true); @@ -170,7 +170,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly', async () => { - const response = await fixture.fetch('/new-site/pt/start'); + const response = await fixture.fetch('/new-site/pt/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Hola'), true); @@ -180,7 +180,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly when using path+codes', async () => { - const response = await fixture.fetch('/new-site/spanish/start'); + const response = await fixture.fetch('/new-site/spanish/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Espanol'), true); @@ -190,12 +190,12 @@ describe('[DEV] i18n routing', () => { }); it("should NOT render the default locale if there isn't a fallback and the route is missing", async () => { - const response = await fixture.fetch('/new-site/it/start'); + const response = await fixture.fetch('/new-site/it/start/'); assert.equal(response.status, 404); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - const response = await fixture.fetch('/new-site/fr/start'); + const response = await fixture.fetch('/new-site/fr/start/'); assert.equal(response.status, 404); }); }); @@ -234,7 +234,7 @@ describe('[DEV] i18n routing', () => { }); it('should render the default locale without prefix', async () => { - const response = await fixture.fetch('/new-site/start'); + const response = await fixture.fetch('/new-site/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); @@ -244,7 +244,7 @@ describe('[DEV] i18n routing', () => { }); it('should return 404 when route contains the default locale', async () => { - const response = await fixture.fetch('/new-site/en/start'); + const response = await fixture.fetch('/new-site/en/start/'); assert.equal(response.status, 404); const response2 = await fixture.fetch('/new-site/en/blog/1'); @@ -252,7 +252,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly', async () => { - const response = await fixture.fetch('/new-site/pt/start'); + const response = await fixture.fetch('/new-site/pt/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Oi essa e start'), true); @@ -262,7 +262,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly when using path+codes', async () => { - const response = await fixture.fetch('/new-site/spanish/start'); + const response = await fixture.fetch('/new-site/spanish/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Espanol'), true); @@ -272,13 +272,13 @@ describe('[DEV] i18n routing', () => { }); it('should redirect to the english locale, which is the first fallback', async () => { - const response = await fixture.fetch('/new-site/it/start'); + const response = await fixture.fetch('/new-site/it/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - const response = await fixture.fetch('/new-site/fr/start'); + const response = await fixture.fetch('/new-site/fr/start/'); assert.equal(response.status, 404); }); }); @@ -386,7 +386,7 @@ describe('[DEV] i18n routing', () => { }); it('should not render the default locale without prefix', async () => { - const response = await fixture.fetch('/new-site/start'); + const response = await fixture.fetch('/new-site/start/'); assert.equal(response.status, 404); assert.equal((await response.text()).includes('Start'), false); @@ -396,7 +396,7 @@ describe('[DEV] i18n routing', () => { }); it('should render the default locale with prefix', async () => { - const response = await fixture.fetch('/new-site/en/start'); + const response = await fixture.fetch('/new-site/en/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); @@ -406,7 +406,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly', async () => { - const response = await fixture.fetch('/new-site/pt/start'); + const response = await fixture.fetch('/new-site/pt/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Oi essa e start'), true); @@ -416,7 +416,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly when using path+codes', async () => { - const response = await fixture.fetch('/new-site/spanish/start'); + const response = await fixture.fetch('/new-site/spanish/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Espanol'), true); @@ -426,12 +426,12 @@ describe('[DEV] i18n routing', () => { }); it('should not redirect to the english locale', async () => { - const response = await fixture.fetch('/new-site/it/start'); + const response = await fixture.fetch('/new-site/it/start/'); assert.equal(response.status, 404); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - const response = await fixture.fetch('/new-site/fr/start'); + const response = await fixture.fetch('/new-site/fr/start/'); assert.equal(response.status, 404); }); }); @@ -497,7 +497,7 @@ describe('[DEV] i18n routing', () => { }); it('should render the default locale without prefix', async () => { - const response = await fixture.fetch('/new-site/start'); + const response = await fixture.fetch('/new-site/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); @@ -507,7 +507,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly', async () => { - const response = await fixture.fetch('/new-site/pt/start'); + const response = await fixture.fetch('/new-site/pt/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Oi essa e start'), true); @@ -517,7 +517,7 @@ describe('[DEV] i18n routing', () => { }); it('should render localised page correctly when using path+codes', async () => { - const response = await fixture.fetch('/new-site/spanish/start'); + const response = await fixture.fetch('/new-site/spanish/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); @@ -527,13 +527,13 @@ describe('[DEV] i18n routing', () => { }); it('should redirect to the english locale, which is the first fallback', async () => { - const response = await fixture.fetch('/new-site/it/start'); + const response = await fixture.fetch('/new-site/it/start/'); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - const response = await fixture.fetch('/new-site/fr/start'); + const response = await fixture.fetch('/new-site/fr/start/'); assert.equal(response.status, 404); }); }); @@ -921,7 +921,7 @@ describe('[SSG] i18n routing', () => { it('should redirect to the english locale correctly when it has codes+path', async () => { let html = await fixture.readFile('/spanish/start/index.html'); assert.equal(html.includes('http-equiv="refresh'), true); - assert.equal(html.includes('url=/new-site/start'), true); + assert.equal(html.includes('url=/new-site/start/'), true); html = await fixture.readFile('/spanish/index.html'); assert.equal(html.includes('http-equiv="refresh'), true); assert.equal(html.includes('url=/new-site'), true); @@ -930,7 +930,7 @@ describe('[SSG] i18n routing', () => { it('should redirect to the english locale, which is the first fallback', async () => { let html = await fixture.readFile('/it/start/index.html'); assert.equal(html.includes('http-equiv="refresh'), true); - assert.equal(html.includes('url=/new-site/start'), true); + assert.equal(html.includes('url=/new-site/start/'), true); html = await fixture.readFile('/it/index.html'); assert.equal(html.includes('http-equiv="refresh'), true); assert.equal(html.includes('url=/new-site'), true); @@ -1008,7 +1008,7 @@ describe('[SSG] i18n routing', () => { it('should render the en locale', async () => { let html = await fixture.readFile('/it/start/index.html'); assert.equal(html.includes('http-equiv="refresh'), true); - assert.equal(html.includes('url=/new-site/en/start'), true); + assert.equal(html.includes('url=/new-site/en/start/'), true); html = await fixture.readFile('/it/index.html'); assert.equal(html.includes('http-equiv="refresh'), true); assert.equal(html.includes('url=/new-site/en'), true); @@ -1200,34 +1200,34 @@ describe('[SSR] i18n routing', () => { }); it('should render the en locale', async () => { - let request = new Request('http://example.com/en/start'); + let request = new Request('http://example.com/en/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); }); it('should render localised page correctly', async () => { - let request = new Request('http://example.com/pt/start'); + let request = new Request('http://example.com/pt/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Oi essa e start'), true); }); it('should render localised page correctly when locale has codes+path', async () => { - let request = new Request('http://example.com/spanish/start'); + let request = new Request('http://example.com/spanish/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Espanol'), true); }); it("should NOT render the default locale if there isn't a fallback and the route is missing", async () => { - let request = new Request('http://example.com/it/start'); + let request = new Request('http://example.com/it/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - let request = new Request('http://example.com/fr/start'); + let request = new Request('http://example.com/fr/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); @@ -1248,27 +1248,27 @@ describe('[SSR] i18n routing', () => { }); it('should render the en locale', async () => { - let request = new Request('http://example.com/en/start'); + let request = new Request('http://example.com/en/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); }); it('should render localised page correctly', async () => { - let request = new Request('http://example.com/new-site/pt/start'); + let request = new Request('http://example.com/new-site/pt/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Oi essa e start'), true); }); it("should NOT render the default locale if there isn't a fallback and the route is missing", async () => { - let request = new Request('http://example.com/new-site/it/start'); + let request = new Request('http://example.com/new-site/it/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - let request = new Request('http://example.com/new-site/fr/start'); + let request = new Request('http://example.com/new-site/fr/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); @@ -1289,40 +1289,40 @@ describe('[SSR] i18n routing', () => { }); it('should render the en locale', async () => { - let request = new Request('http://example.com/new-site/start'); + let request = new Request('http://example.com/new-site/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); }); it('should return 404 if route contains the default locale', async () => { - let request = new Request('http://example.com/new-site/en/start'); + let request = new Request('http://example.com/new-site/en/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); it('should render localised page correctly', async () => { - let request = new Request('http://example.com/new-site/pt/start'); + let request = new Request('http://example.com/new-site/pt/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Oi essa e start'), true); }); it('should render localised page correctly when locale has codes+path', async () => { - let request = new Request('http://example.com/new-site/spanish/start'); + let request = new Request('http://example.com/new-site/spanish/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Espanol'), true); }); it("should NOT render the default locale if there isn't a fallback and the route is missing", async () => { - let request = new Request('http://example.com/new-site/it/start'); + let request = new Request('http://example.com/new-site/it/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - let request = new Request('http://example.com/new-site/fr/start'); + let request = new Request('http://example.com/new-site/fr/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); @@ -1354,14 +1354,14 @@ describe('[SSR] i18n routing', () => { }); it('should NOT redirect the index to the default locale', async () => { - let request = new Request('http://example.com/new-site'); + let request = new Request('http://example.com/new-site/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('I am index'), true); }); it('can render the 404.astro route on unmatched requests', async () => { - const request = new Request('http://example.com/xyz'); + const request = new Request('http://example.com/xyz/'); const response = await app.render(request); assert.equal(response.status, 404); const text = await response.text(); @@ -1384,41 +1384,41 @@ describe('[SSR] i18n routing', () => { }); it('should redirect the index to the default locale', async () => { - let request = new Request('http://example.com/new-site'); + let request = new Request('http://example.com/new-site/'); let response = await app.render(request); assert.equal(response.status, 302); assert.equal(response.headers.get('location'), '/new-site/en/'); }); it('should render the en locale', async () => { - let request = new Request('http://example.com/new-site/en/start'); + let request = new Request('http://example.com/new-site/en/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); }); it('should render localised page correctly', async () => { - let request = new Request('http://example.com/pt/start'); + let request = new Request('http://example.com/pt/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Oi essa e start'), true); }); it('should render localised page correctly when locale has codes+path', async () => { - let request = new Request('http://example.com/spanish/start'); + let request = new Request('http://example.com/spanish/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Espanol'), true); }); it("should NOT render the default locale if there isn't a fallback and the route is missing", async () => { - let request = new Request('http://example.com/it/start'); + let request = new Request('http://example.com/it/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - let request = new Request('http://example.com/fr/start'); + let request = new Request('http://example.com/fr/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); @@ -1497,35 +1497,35 @@ describe('[SSR] i18n routing', () => { }); it('should render the en locale', async () => { - let request = new Request('http://example.com/new-site/start'); + let request = new Request('http://example.com/new-site/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Start'), true); }); it('should render localised page correctly', async () => { - let request = new Request('http://example.com/new-site/pt/start'); + let request = new Request('http://example.com/new-site/pt/start/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Oi essa e start'), true); }); it('should redirect to the english locale, which is the first fallback', async () => { - let request = new Request('http://example.com/new-site/it/start'); + let request = new Request('http://example.com/new-site/it/start/'); let response = await app.render(request); assert.equal(response.status, 302); - assert.equal(response.headers.get('location'), '/new-site/start'); + assert.equal(response.headers.get('location'), '/new-site/start/'); }); it('should redirect to the english locale when locale has codes+path', async () => { - let request = new Request('http://example.com/new-site/spanish/start'); + let request = new Request('http://example.com/new-site/spanish/start/'); let response = await app.render(request); assert.equal(response.status, 302); - assert.equal(response.headers.get('location'), '/new-site/start'); + assert.equal(response.headers.get('location'), '/new-site/start/'); }); it("should render a 404 because the route `fr` isn't included in the list of locales of the configuration", async () => { - let request = new Request('http://example.com/new-site/fr/start'); + let request = new Request('http://example.com/new-site/fr/start/'); let response = await app.render(request); assert.equal(response.status, 404); }); @@ -1552,10 +1552,10 @@ describe('[SSR] i18n routing', () => { }); it('should redirect to the english locale, which is the first fallback', async () => { - let request = new Request('http://example.com/new-site/it/start'); + let request = new Request('http://example.com/new-site/it/start/'); let response = await app.render(request); assert.equal(response.status, 302); - assert.equal(response.headers.get('location'), '/new-site/start'); + assert.equal(response.headers.get('location'), '/new-site/start/'); }); }); }); @@ -1575,7 +1575,7 @@ describe('[SSR] i18n routing', () => { }); it('should not render the locale when the value is *', async () => { - let request = new Request('http://example.com/preferred-locale', { + let request = new Request('http://example.com/preferred-locale/', { headers: { 'Accept-Language': '*', }, @@ -1586,7 +1586,7 @@ describe('[SSR] i18n routing', () => { }); it('should render the locale pt', async () => { - let request = new Request('http://example.com/preferred-locale', { + let request = new Request('http://example.com/preferred-locale/', { headers: { 'Accept-Language': 'pt', }, @@ -1597,7 +1597,7 @@ describe('[SSR] i18n routing', () => { }); it('should render empty locales', async () => { - let request = new Request('http://example.com/preferred-locale', { + let request = new Request('http://example.com/preferred-locale/', { headers: { 'Accept-Language': 'fr;q=0.1,fr-AU;q=0.9', }, @@ -1610,7 +1610,7 @@ describe('[SSR] i18n routing', () => { }); it('should render none as preferred locale, but have a list of locales that correspond to the initial locales', async () => { - let request = new Request('http://example.com/preferred-locale', { + let request = new Request('http://example.com/preferred-locale/', { headers: { 'Accept-Language': '*', }, @@ -1643,7 +1643,7 @@ describe('[SSR] i18n routing', () => { }); it('they should be still considered when parsing the Accept-Language header', async () => { - let request = new Request('http://example.com/preferred-locale', { + let request = new Request('http://example.com/preferred-locale/', { headers: { 'Accept-Language': 'en-AU;q=0.1,pt-BR;q=0.9', }, @@ -1668,7 +1668,7 @@ describe('[SSR] i18n routing', () => { }); it('they should be still considered when parsing the Accept-Language header', async () => { - let request = new Request('http://example.com/preferred-locale', { + let request = new Request('http://example.com/preferred-locale/', { headers: { 'Accept-Language': 'en-AU;q=0.1,es;q=0.9', }, @@ -1698,28 +1698,28 @@ describe('[SSR] i18n routing', () => { }); it('should return the default locale', async () => { - let request = new Request('http://example.com/current-locale', {}); + let request = new Request('http://example.com/current-locale/', {}); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Current Locale: en'), true); }); it('should return the default locale when rendering a route with spread operator', async () => { - let request = new Request('http://example.com/blog/es', {}); + let request = new Request('http://example.com/blog/es/', {}); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Current Locale: es'), true); }); it('should return the default locale of the current URL', async () => { - let request = new Request('http://example.com/pt/start', {}); + let request = new Request('http://example.com/pt/start/', {}); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Current Locale: pt'), true); }); it('should return the default locale when a route is dynamic', async () => { - let request = new Request('http://example.com/dynamic/lorem', {}); + let request = new Request('http://example.com/dynamic/lorem/', {}); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Current Locale: en'), true); @@ -1741,14 +1741,14 @@ describe('[SSR] i18n routing', () => { }); it('should return the locale of the current URL (en)', async () => { - let request = new Request('http://example.com/en/start', {}); + let request = new Request('http://example.com/en/start/', {}); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Current Locale: en'), true); }); it('should return the locale of the current URL (pt)', async () => { - let request = new Request('http://example.com/pt/start', {}); + let request = new Request('http://example.com/pt/start/', {}); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Current Locale: pt'), true); @@ -1844,7 +1844,7 @@ describe('i18n routing does not break assets and endpoints', () => { }); it('should render the en locale when X-Forwarded-Host header is passed', async () => { - let request = new Request('http://example.pt/start', { + let request = new Request('http://example.pt/start/', { headers: { 'X-Forwarded-Host': 'example.pt', 'X-Forwarded-Proto': 'https', @@ -1856,7 +1856,7 @@ describe('i18n routing does not break assets and endpoints', () => { }); it('should render the en locale when Host header is passed', async () => { - let request = new Request('http://example.pt/start', { + let request = new Request('http://example.pt/start/', { headers: { Host: 'example.pt', 'X-Forwarded-Proto': 'https', @@ -1868,7 +1868,7 @@ describe('i18n routing does not break assets and endpoints', () => { }); it('should render the en locale when Host header is passed and it has the port', async () => { - let request = new Request('http://example.pt/start', { + let request = new Request('http://example.pt/start/', { headers: { Host: 'example.pt:8080', 'X-Forwarded-Proto': 'https', @@ -1880,7 +1880,7 @@ describe('i18n routing does not break assets and endpoints', () => { }); it('should render when the protocol header we fallback to the one of the host', async () => { - let request = new Request('https://example.pt/start', { + let request = new Request('https://example.pt/start/', { headers: { Host: 'example.pt', }, @@ -1957,7 +1957,7 @@ describe('Fallback rewrite dev server', () => { }); it('should correctly rewrite to en', async () => { - const html = await fixture.fetch('/fr').then((res) => res.text()); + const html = await fixture.fetch('/fr/').then((res) => res.text()); assert.match(html, /Hello/); // assert.fail() }); @@ -2025,7 +2025,7 @@ describe('Fallback rewrite SSR', () => { }); it('should correctly rewrite to en', async () => { - const request = new Request('http://example.com/fr'); + const request = new Request('http://example.com/fr/'); const response = await app.render(request); assert.equal(response.status, 200); const html = await response.text(); diff --git a/packages/astro/test/middleware.test.js b/packages/astro/test/middleware.test.js index f7fbc3501c5b..46095ccfc962 100644 --- a/packages/astro/test/middleware.test.js +++ b/packages/astro/test/middleware.test.js @@ -39,33 +39,33 @@ describe('Middleware in DEV mode', () => { }); it('should call a second middleware', async () => { - const html = await fixture.fetch('/second').then((res) => res.text()); + const html = await fixture.fetch('/second/').then((res) => res.text()); const $ = cheerio.load(html); assert.equal($('p').html(), 'second'); }); it('should successfully create a new response', async () => { - const html = await fixture.fetch('/rewrite').then((res) => res.text()); + const html = await fixture.fetch('/rewrite/').then((res) => res.text()); const $ = cheerio.load(html); assert.equal($('p').html(), null); assert.equal($('span').html(), 'New content!!'); }); it('should return a new response that is a 500', async () => { - await fixture.fetch('/broken-500').then((res) => { + await fixture.fetch('/broken-500/').then((res) => { assert.equal(res.status, 500); return res.text(); }); }); it('should successfully render a page if the middleware calls only next() and returns nothing', async () => { - const html = await fixture.fetch('/not-interested').then((res) => res.text()); + const html = await fixture.fetch('/not-interested/').then((res) => res.text()); const $ = cheerio.load(html); assert.equal($('p').html(), 'Not interested'); }); it("should throw an error when the middleware doesn't call next or doesn't return a response", async () => { - const html = await fixture.fetch('/does-nothing').then((res) => res.text()); + const html = await fixture.fetch('/does-nothing/').then((res) => res.text()); const $ = cheerio.load(html); assert.equal($('title').html(), 'MiddlewareNoDataOrNextCalled'); }); @@ -76,26 +76,26 @@ describe('Middleware in DEV mode', () => { }); it('should be able to clone the response', async () => { - const res = await fixture.fetch('/clone'); + const res = await fixture.fetch('/clone/'); const html = await res.text(); assert.equal(html.includes('it works'), true); }); it('should forward cookies set in a component when the middleware returns a new response', async () => { - const res = await fixture.fetch('/return-response-cookies'); + const res = await fixture.fetch('/return-response-cookies/'); const headers = res.headers; assert.notEqual(headers.get('set-cookie'), null); }); describe('Integration hooks', () => { it('Integration middleware marked as "pre" runs', async () => { - const res = await fixture.fetch('/integration-pre'); + const res = await fixture.fetch('/integration-pre/'); const json = await res.json(); assert.equal(json.pre, 'works'); }); it('Integration middleware marked as "post" runs', async () => { - const res = await fixture.fetch('/integration-post'); + const res = await fixture.fetch('/integration-post/'); const json = await res.json(); assert.equal(json.post, 'works'); }); @@ -189,27 +189,27 @@ describe('Middleware API in PROD mode, SSR', () => { let $ = cheerio.load(html); assert.equal($('p').html(), 'bar'); - response = await app.render(new Request('http://example.com/lorem')); + response = await app.render(new Request('http://example.com/lorem/')); html = await response.text(); $ = cheerio.load(html); assert.equal($('p').html(), 'ipsum'); }); it('should successfully redirect to another page', async () => { - const request = new Request('http://example.com/redirect'); + const request = new Request('http://example.com/redirect/'); const response = await app.render(request); assert.equal(response.status, 302); }); it('should call a second middleware', async () => { - const response = await app.render(new Request('http://example.com/second')); + const response = await app.render(new Request('http://example.com/second/')); const html = await response.text(); const $ = cheerio.load(html); assert.equal($('p').html(), 'second'); }); it('should successfully create a new response', async () => { - const request = new Request('http://example.com/rewrite'); + const request = new Request('http://example.com/rewrite/'); const response = await app.render(request); const html = await response.text(); const $ = cheerio.load(html); @@ -218,13 +218,13 @@ describe('Middleware API in PROD mode, SSR', () => { }); it('should return a new response that is a 500', async () => { - const request = new Request('http://example.com/broken-500'); + const request = new Request('http://example.com/broken-500/'); const response = await app.render(request); assert.equal(response.status, 500); }); it('should successfully render a page if the middleware calls only next() and returns nothing', async () => { - const request = new Request('http://example.com/not-interested'); + const request = new Request('http://example.com/not-interested/'); const response = await app.render(request); const html = await response.text(); const $ = cheerio.load(html); @@ -232,7 +232,7 @@ describe('Middleware API in PROD mode, SSR', () => { }); it("should throw an error when the middleware doesn't call next or doesn't return a response", async () => { - const request = new Request('http://example.com/does-nothing'); + const request = new Request('http://example.com/does-nothing/'); const response = await app.render(request); const html = await response.text(); const $ = cheerio.load(html); @@ -240,21 +240,21 @@ describe('Middleware API in PROD mode, SSR', () => { }); it('should correctly work for API endpoints that return a Response object', async () => { - const request = new Request('http://example.com/api/endpoint'); + const request = new Request('http://example.com/api/endpoint/'); const response = await app.render(request); assert.equal(response.status, 200); assert.equal(response.headers.get('Content-Type'), 'application/json'); }); it('should correctly manipulate the response coming from API endpoints (not simple)', async () => { - const request = new Request('http://example.com/api/endpoint'); + const request = new Request('http://example.com/api/endpoint/'); const response = await app.render(request); const text = await response.text(); assert.equal(text.includes('REDACTED'), true); }); it('should correctly call the middleware function for 404', async () => { - const request = new Request('http://example.com/funky-url'); + const request = new Request('http://example.com/funky-url/'); const routeData = app.match(request); const response = await app.render(request, { routeData }); const text = await response.text(); @@ -263,7 +263,7 @@ describe('Middleware API in PROD mode, SSR', () => { }); it('should render 500.astro when the middleware throws an error', async () => { - const request = new Request('http://example.com/throw'); + const request = new Request('http://example.com/throw/'); const routeData = app.match(request); const response = await app.render(request, { routeData }); diff --git a/packages/astro/test/preview-routing.test.js b/packages/astro/test/preview-routing.test.js index eed735c7684d..20983e01dea8 100644 --- a/packages/astro/test/preview-routing.test.js +++ b/packages/astro/test/preview-routing.test.js @@ -208,8 +208,8 @@ describe('Preview Routing', () => { await previewServer.stop(); }); - it('renders custom 404 for /a', async () => { - const res = await fixture.fetch('/a'); + it('renders custom 404 for /a/', async () => { + const res = await fixture.fetch('/a/'); assert.equal(res.status, 404); const html = await res.text(); diff --git a/packages/astro/test/public-base-404.test.js b/packages/astro/test/public-base-404.test.js index 12c5ce14c6b4..52837bfa1a75 100644 --- a/packages/astro/test/public-base-404.test.js +++ b/packages/astro/test/public-base-404.test.js @@ -50,7 +50,7 @@ describe('Public dev with base', () => { assert.equal(response.status, 404); const html = await response.text(); $ = cheerio.load(html); - assert.equal($('a').first().text(), '/blog'); + assert.equal($('a').first().text(), '/blog/'); }); it('default 404 page when loading /none/', async () => { diff --git a/packages/astro/test/redirects.test.js b/packages/astro/test/redirects.test.js index 8014fb73bb45..58c34b9c18ab 100644 --- a/packages/astro/test/redirects.test.js +++ b/packages/astro/test/redirects.test.js @@ -3,6 +3,9 @@ import { after, before, describe, it } from 'node:test'; import testAdapter from './test-adapter.js'; import { loadFixture } from './test-utils.js'; +// TODO: trailingSlash +// How does redirect interact with trailingSlash + describe('Astro.redirect', () => { /** @type {import('./test-utils').Fixture} */ let fixture; @@ -71,7 +74,7 @@ describe('Astro.redirect', () => { it('Uses 308 for non-GET methods', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/api/redirect', { + const request = new Request('http://example.com/api/redirect/', { method: 'POST', }); const response = await app.render(request); @@ -80,28 +83,28 @@ describe('Astro.redirect', () => { it('Forwards params to the target path - single param', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/source/x'); + const request = new Request('http://example.com/source/x/'); const response = await app.render(request); assert.equal(response.headers.get('Location'), '/not-verbatim/target1/x'); }); it('Forwards params to the target path - multiple params', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/source/x/y'); + const request = new Request('http://example.com/source/x/y/'); const response = await app.render(request); assert.equal(response.headers.get('Location'), '/not-verbatim/target2/x/y'); }); it('Forwards params to the target path - spread param', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/source/x/y/z'); + const request = new Request('http://example.com/source/x/y/z/'); const response = await app.render(request); assert.equal(response.headers.get('Location'), '/not-verbatim/target3/x/y/z'); }); it('Forwards params to the target path - special characters', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/source/Las Vegas’'); + const request = new Request('http://example.com/source/Las Vegas’/'); const response = await app.render(request); assert.equal( response.headers.get('Location'), @@ -233,7 +236,7 @@ describe('Astro.redirect', () => { }); it('performs simple redirects', async () => { - let res = await fixture.fetch('/one', { + let res = await fixture.fetch('/one/', { redirect: 'manual', }); assert.equal(res.status, 301); @@ -241,25 +244,25 @@ describe('Astro.redirect', () => { }); it('performs dynamic redirects', async () => { - const response = await fixture.fetch('/more/old/hello', { redirect: 'manual' }); + const response = await fixture.fetch('/more/old/hello/', { redirect: 'manual' }); assert.equal(response.status, 301); assert.equal(response.headers.get('Location'), '/more/hello'); }); it('performs dynamic redirects with special characters', async () => { // encodeURI("/more/old/’") - const response = await fixture.fetch('/more/old/%E2%80%99', { redirect: 'manual' }); + const response = await fixture.fetch('/more/old/%E2%80%99/', { redirect: 'manual' }); assert.equal(response.status, 301); assert.equal(response.headers.get('Location'), '/more/%E2%80%99'); }); it('performs dynamic redirects with multiple params', async () => { - const response = await fixture.fetch('/more/old/hello/world', { redirect: 'manual' }); + const response = await fixture.fetch('/more/old/hello/world/', { redirect: 'manual' }); assert.equal(response.headers.get('Location'), '/more/hello/world'); }); it.skip('falls back to spread rule when dynamic rules should not match', async () => { - const response = await fixture.fetch('/more/old/welcome/world', { redirect: 'manual' }); + const response = await fixture.fetch('/more/old/welcome/world/', { redirect: 'manual' }); assert.equal(response.headers.get('Location'), '/more/new/welcome/world'); }); }); diff --git a/packages/astro/test/reuse-injected-entrypoint.test.js b/packages/astro/test/reuse-injected-entrypoint.test.js index 10b8e528f852..441204b2dcab 100644 --- a/packages/astro/test/reuse-injected-entrypoint.test.js +++ b/packages/astro/test/reuse-injected-entrypoint.test.js @@ -10,36 +10,36 @@ const routes = [ h1: 'index.astro', }, { - description: 'matches /injected-a to to-inject.astro', - url: '/injected-a', + description: 'matches /injected-a/ to to-inject.astro', + url: '/injected-a/', h1: 'to-inject.astro', }, { - description: 'matches /injected-b to to-inject.astro', - url: '/injected-b', + description: 'matches /injected-b/ to to-inject.astro', + url: '/injected-b/', h1: 'to-inject.astro', }, { - description: 'matches /dynamic-a/id-1 to [id].astro', - url: '/dynamic-a/id-1', + description: 'matches /dynamic-a/id-1/ to [id].astro', + url: '/dynamic-a/id-1/', h1: '[id].astro', p: 'id-1', }, { - description: 'matches /dynamic-a/id-2 to [id].astro', - url: '/dynamic-a/id-2', + description: 'matches /dynamic-a/id-2/ to [id].astro', + url: '/dynamic-a/id-2/', h1: '[id].astro', p: 'id-2', }, { description: 'matches /dynamic-b/id-1 to [id].astro', - url: '/dynamic-b/id-1', + url: '/dynamic-b/id-1/', h1: '[id].astro', p: 'id-1', }, { - description: 'matches /dynamic-b/id-2 to [id].astro', - url: '/dynamic-b/id-2', + description: 'matches /dynamic-b/id-2/ to [id].astro', + url: '/dynamic-b/id-2/', h1: '[id].astro', p: 'id-2', }, diff --git a/packages/astro/test/rewrite.test.js b/packages/astro/test/rewrite.test.js index 10d7c70d24fd..491be20168fe 100644 --- a/packages/astro/test/rewrite.test.js +++ b/packages/astro/test/rewrite.test.js @@ -20,36 +20,36 @@ describe('Dev reroute', () => { await devServer.stop(); }); - it('should render the index page when navigating /reroute ', async () => { - const html = await fixture.fetch('/reroute').then((res) => res.text()); + it('should render the index page when navigating /reroute/', async () => { + const html = await fixture.fetch('/reroute/').then((res) => res.text()); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Index'); }); - it('should render the index page when navigating /blog/hello ', async () => { - const html = await fixture.fetch('/blog/hello').then((res) => res.text()); + it('should render the index page when navigating /blog/hello/', async () => { + const html = await fixture.fetch('/blog/hello/').then((res) => res.text()); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Index'); }); - it('should render the index page when navigating /blog/salut ', async () => { - const html = await fixture.fetch('/blog/salut').then((res) => res.text()); + it('should render the index page when navigating /blog/salut/', async () => { + const html = await fixture.fetch('/blog/salut/').then((res) => res.text()); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Index'); }); - it('should render the index page when navigating dynamic route /dynamic/[id] ', async () => { - const html = await fixture.fetch('/dynamic/hello').then((res) => res.text()); + it('should render the index page when navigating dynamic route /dynamic/[id]/', async () => { + const html = await fixture.fetch('/dynamic/hello/').then((res) => res.text()); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Index'); }); - it('should render the index page when navigating spread route /spread/[...spread] ', async () => { - const html = await fixture.fetch('/spread/hello').then((res) => res.text()); + it('should render the index page when navigating spread route /spread/[...spread]/', async () => { + const html = await fixture.fetch('/spread/hello/').then((res) => res.text()); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Index'); @@ -139,7 +139,7 @@ describe('Dev rewrite, hybrid/server', () => { const formData = new FormData(); formData.append('email', 'example@example.com'); - const request = new Request('http://example.com/post/post-body-used', { + const request = new Request('http://example.com/post/post-body-used/', { method: 'POST', body: formData, }); @@ -184,14 +184,14 @@ describe('Build reroute', () => { assert.equal($('h1').text(), 'Index'); }); - it('should create the index page when navigating dynamic route /dynamic/[id] ', async () => { + it('should create the index page when navigating dynamic route /dynamic/[id]/', async () => { const html = await fixture.readFile('/dynamic/hello/index.html'); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Index'); }); - it('should create the index page when navigating spread route /spread/[...spread] ', async () => { + it('should create the index page when navigating spread route /spread/[...spread]/', async () => { const html = await fixture.readFile('/spread/hello/index.html'); const $ = cheerioLoad(html); @@ -238,8 +238,8 @@ describe('SSR reroute', () => { app = await fixture.loadTestAdapterApp(); }); - it('should render the index page when navigating /reroute ', async () => { - const request = new Request('http://example.com/reroute'); + it('should render the index page when navigating /reroute/', async () => { + const request = new Request('http://example.com/reroute/'); const response = await app.render(request); const html = await response.text(); const $ = cheerioLoad(html); @@ -247,8 +247,8 @@ describe('SSR reroute', () => { assert.equal($('h1').text(), 'Index'); }); - it('should render the index page when navigating /blog/hello ', async () => { - const request = new Request('http://example.com/blog/hello'); + it('should render the index page when navigating /blog/hello/', async () => { + const request = new Request('http://example.com/blog/hello/'); const response = await app.render(request); const html = await response.text(); const $ = cheerioLoad(html); @@ -256,8 +256,8 @@ describe('SSR reroute', () => { assert.equal($('h1').text(), 'Index'); }); - it('should render the index page when navigating /blog/salut ', async () => { - const request = new Request('http://example.com/blog/salut'); + it('should render the index page when navigating /blog/salut/', async () => { + const request = new Request('http://example.com/blog/salut/'); const response = await app.render(request); const html = await response.text(); @@ -266,8 +266,8 @@ describe('SSR reroute', () => { assert.equal($('h1').text(), 'Index'); }); - it('should render the index page when navigating dynamic route /dynamic/[id] ', async () => { - const request = new Request('http://example.com/dynamic/hello'); + it('should render the index page when navigating dynamic route /dynamic/[id]/', async () => { + const request = new Request('http://example.com/dynamic/hello/'); const response = await app.render(request); const html = await response.text(); const $ = cheerioLoad(html); @@ -275,8 +275,8 @@ describe('SSR reroute', () => { assert.equal($('h1').text(), 'Index'); }); - it('should render the index page when navigating spread route /spread/[...spread] ', async () => { - const request = new Request('http://example.com/spread/hello'); + it('should render the index page when navigating spread route /spread/[...spread]/', async () => { + const request = new Request('http://example.com/spread/hello/'); const response = await app.render(request); const html = await response.text(); const $ = cheerioLoad(html); @@ -285,13 +285,13 @@ describe('SSR reroute', () => { }); it('should render the 404 built-in page', async () => { - const request = new Request('http://example.com/blog/oops'); + const request = new Request('http://example.com/blog/oops/'); const response = await app.render(request); assert.equal(response.status, 404); }); it('should pass the POST data from one page to another', async () => { - const request = new Request('http://example.com/post/post-a', { + const request = new Request('http://example.com/post/post-a/', { method: 'POST', body: JSON.stringify({ email: 'example@example.com', @@ -331,8 +331,6 @@ describe('SSR rewrite, hybrid/server', () => { const html = await response.text(); const $ = cheerioLoad(html); - console.log(html); - assert.match($('h1').text(), /Title/); assert.match($('p').text(), /some-slug/); }); @@ -341,7 +339,7 @@ describe('SSR rewrite, hybrid/server', () => { const formData = new FormData(); formData.append('email', 'example@example.com'); - const request = new Request('http://example.com/post/post-body-used', { + const request = new Request('http://example.com/post/post-body-used/', { method: 'POST', body: formData, }); @@ -407,7 +405,7 @@ describe('Middleware with custom 404.astro and 500.astro', () => { }); it('The `next()` function should return a Response with status code 404', async () => { - const html = await fixture.fetch('/about').then((res) => res.text()); + const html = await fixture.fetch('/about/').then((res) => res.text()); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Custom error'); @@ -415,7 +413,7 @@ describe('Middleware with custom 404.astro and 500.astro', () => { }); it('The `next()` function should return a Response with status code 500', async () => { - const html = await fixture.fetch('/about-2').then((res) => res.text()); + const html = await fixture.fetch('/about-2/').then((res) => res.text()); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Custom error'); @@ -440,7 +438,7 @@ describe('Runtime error, default 500', () => { }); it('should return a 500 status code, but not render the custom 500', async () => { - const response = await fixture.fetch('/errors/from'); + const response = await fixture.fetch('/errors/from/'); assert.equal(response.status, 500); const text = await response.text(); assert.match(text, /@vite\/client/); @@ -463,7 +461,7 @@ describe('Runtime error in SSR, default 500', () => { }); it('should return a 500 status code, but not render the custom 500', async () => { - const request = new Request('http://example.com/errors/from'); + const request = new Request('http://example.com/errors/from/'); const response = await app.render(request); const text = await response.text(); assert.equal(text, ''); @@ -487,7 +485,7 @@ describe('Runtime error in dev, custom 500', () => { }); it('should render the custom 500 when rewriting a page that throws an error', async () => { - const response = await fixture.fetch('/errors/start'); + const response = await fixture.fetch('/errors/start/'); assert.equal(response.status, 500); const html = await response.text(); assert.match(html, /I am the custom 500/); @@ -510,7 +508,7 @@ describe('Runtime error in SSR, custom 500', () => { }); it('should render the custom 500 when rewriting a page that throws an error', async () => { - const request = new Request('http://example.com/errors/start'); + const request = new Request('http://example.com/errors/start/'); const response = await app.render(request); const html = await response.text(); diff --git a/packages/astro/test/routing-priority.test.js b/packages/astro/test/routing-priority.test.js index d4203595d8a9..ee8ee91b6b4d 100644 --- a/packages/astro/test/routing-priority.test.js +++ b/packages/astro/test/routing-priority.test.js @@ -2,6 +2,7 @@ import assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import { load as cheerioLoad } from 'cheerio'; import { loadFixture } from './test-utils.js'; +// import { removeTrailingForwardSlash } from '@astrojs/internal-helpers/path'; const routes = [ { @@ -10,95 +11,95 @@ const routes = [ h1: 'index.astro', }, { - description: 'matches /slug-1 to [slug].astro', - url: '/slug-1', + description: 'matches /slug-1/ to [slug].astro', + url: '/slug-1/', h1: '[slug].astro', p: 'slug-1', }, { - description: 'matches /slug-2 to [slug].astro', - url: '/slug-2', + description: 'matches /slug-2/ to [slug].astro', + url: '/slug-2/', h1: '[slug].astro', p: 'slug-2', }, { - description: 'matches /page-1 to [page].astro', - url: '/page-1', + description: 'matches /page-1/ to [page].astro', + url: '/page-1/', h1: '[page].astro', p: 'page-1', }, { - description: 'matches /page-2 to [page].astro', - url: '/page-2', + description: 'matches /page-2/ to [page].astro', + url: '/page-2/', h1: '[page].astro', p: 'page-2', }, { - description: 'matches /posts/post-1 to posts/[pid].astro', - url: '/posts/post-1', + description: 'matches /posts/post-1/ to posts/[pid].astro', + url: '/posts/post-1/', h1: 'posts/[pid].astro', p: 'post-1', }, { - description: 'matches /posts/post-2 to posts/[pid].astro', - url: '/posts/post-2', + description: 'matches /posts/post-2/ to posts/[pid].astro', + url: '/posts/post-2/', h1: 'posts/[pid].astro', p: 'post-2', }, { - description: 'matches /posts/1/2 to posts/[...slug].astro', - url: '/posts/1/2', + description: 'matches /posts/1/2/ to posts/[...slug].astro', + url: '/posts/1/2/', h1: 'posts/[...slug].astro', p: '1/2', }, { - description: 'matches /de to de/index.astro', - url: '/de', + description: 'matches /de/ to de/index.astro', + url: '/de/', h1: 'de/index.astro (priority)', }, { - description: 'matches /en to [lang]/index.astro', - url: '/en', + description: 'matches /en/ to [lang]/index.astro', + url: '/en/', h1: '[lang]/index.astro', p: 'en', }, { - description: 'matches /de/1/2 to [lang]/[...catchall].astro', - url: '/de/1/2', + description: 'matches /de/1/2/ to [lang]/[...catchall].astro', + url: '/de/1/2/', h1: '[lang]/[...catchall].astro', p: 'de | 1/2', }, { - description: 'matches /en/1/2 to [lang]/[...catchall].astro', - url: '/en/1/2', + description: 'matches /en/1/2/ to [lang]/[...catchall].astro', + url: '/en/1/2/', h1: '[lang]/[...catchall].astro', p: 'en | 1/2', }, { - description: 'matches /injected to to-inject.astro', - url: '/injected', + description: 'matches /injected/ to to-inject.astro', + url: '/injected/', h1: 'to-inject.astro', }, { - description: 'matches /_injected to to-inject.astro', - url: '/_injected', + description: 'matches /_injected/ to to-inject.astro', + url: '/_injected/', h1: 'to-inject.astro', }, { - description: 'matches /injected-1 to [id].astro', - url: '/injected-1', + description: 'matches /injected-1/ to [id].astro', + url: '/injected-1/', h1: '[id].astro', p: 'injected-1', }, { - description: 'matches /injected-2 to [id].astro', - url: '/injected-2', + description: 'matches /injected-2/ to [id].astro', + url: '/injected-2/', h1: '[id].astro', p: 'injected-2', }, { - description: 'matches /empty-slug to empty-slug/[...slug].astro', - url: '/empty-slug', + description: 'matches /empty-slug/ to empty-slug/[...slug].astro', + url: '/empty-slug/', h1: 'empty-slug/[...slug].astro', p: 'slug: ', }, @@ -213,28 +214,29 @@ describe('Routing priority', () => { // skip for endpoint page test if (isEndpoint) return; - // checks with trailing slashes, ex: '/de/' instead of '/de' - it(`${description} (trailing slash)`, async () => { - const html = await fixture.fetch(appendForwardSlash(url)).then((res) => res.text()); - const $ = cheerioLoad(html); - - if (fourOhFour) { - assert.equal($('title').text(), '404: Not Found'); - return; - } - - if (h1) { - assert.equal($('h1').text(), h1); - } - - if (p) { - assert.equal($('p').text(), p); - } - - if (htmlMatch) { - assert.equal(html, htmlMatch); - } - }); + // checks with trailing slashes, ex: '/de' instead of '/de/'] + // TODO: trailingSlash + // it(`${description} (no trailing slash)`, async () => { + // const html = await fixture.fetch(removeTrailingForwardSlash(url)).then((res) => res.text()); + // const $ = cheerioLoad(html); + + // if (fourOhFour) { + // assert.equal($('title').text(), '404: Not Found'); + // return; + // } + + // if (h1) { + // assert.equal($('h1').text(), h1); + // } + + // if (p) { + // assert.equal($('p').text(), p); + // } + + // if (htmlMatch) { + // assert.equal(html, htmlMatch); + // } + // }); // checks with index.html, ex: '/de/index.html' instead of '/de' it(`${description} (index.html)`, async () => { diff --git a/packages/astro/test/set-html.test.js b/packages/astro/test/set-html.test.js index c3a100d0449e..473ba25ea0c5 100644 --- a/packages/astro/test/set-html.test.js +++ b/packages/astro/test/set-html.test.js @@ -29,7 +29,7 @@ describe('set:html', () => { }); it('can take a fetch()', async () => { - let res = await fixture.fetch('/fetch'); + let res = await fixture.fetch('/fetch/'); assert.equal(res.status, 200); let html = await res.text(); const $ = cheerio.load(html); @@ -37,7 +37,7 @@ describe('set:html', () => { assert.equal($('#fetched-html').text(), 'works'); }); it('test Fragment when Fragment is as a slot', async () => { - let res = await fixture.fetch('/children'); + let res = await fixture.fetch('/children/'); assert.equal(res.status, 200); let html = await res.text(); assert.equal(html.includes('Test'), true); diff --git a/packages/astro/test/special-chars-in-component-imports.test.js b/packages/astro/test/special-chars-in-component-imports.test.js index 64301741adff..48127a39b1dd 100644 --- a/packages/astro/test/special-chars-in-component-imports.test.js +++ b/packages/astro/test/special-chars-in-component-imports.test.js @@ -111,7 +111,7 @@ describe('Special chars in component import paths', () => { }); it('Special chars in imports work from .mdx files', async () => { - const html = await fixture.fetch('/mdx').then((res) => res.text()); + const html = await fixture.fetch('/mdx/').then((res) => res.text()); const $ = cheerioLoad(html); // Test 1: Correct page diff --git a/packages/astro/test/ssr-api-route.test.js b/packages/astro/test/ssr-api-route.test.js index 2e7405326fcc..5b186b235ad3 100644 --- a/packages/astro/test/ssr-api-route.test.js +++ b/packages/astro/test/ssr-api-route.test.js @@ -38,7 +38,7 @@ describe('API routes in SSR', () => { }); it('Has valid api context', async () => { - const request = new Request('http://example.com/context/any'); + const request = new Request('http://example.com/context/any/'); const response = await app.render(request); assert.equal(response.status, 200); const data = await response.json(); @@ -86,7 +86,7 @@ describe('API routes in SSR', () => { const file = new File([raw], 'penguin.jpg', { type: 'text/jpg' }); formData.set('file', file, 'penguin.jpg'); - const res = await fixture.fetch('/binary', { + const res = await fixture.fetch('/binary/', { method: 'POST', body: formData, }); @@ -126,7 +126,7 @@ describe('API routes in SSR', () => { }); it('Has valid api context', async () => { - const response = await fixture.fetch('/context/any'); + const response = await fixture.fetch('/context/any/'); assert.equal(response.status, 200); const data = await response.json(); assert.equal(data.cookiesExist, true); diff --git a/packages/astro/test/ssr-dynamic.test.js b/packages/astro/test/ssr-dynamic.test.js index b532ac929338..7d5fbe103dcb 100644 --- a/packages/astro/test/ssr-dynamic.test.js +++ b/packages/astro/test/ssr-dynamic.test.js @@ -71,24 +71,24 @@ describe('Dynamic pages in SSR', () => { } it('Do not have to implement getStaticPaths', async () => { - const html = await fetchHTML('/123'); + const html = await fetchHTML('/123/'); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Item 123'); }); it('Includes page styles', async () => { - const html = await fetchHTML('/123'); + const html = await fetchHTML('/123/'); const $ = cheerioLoad(html); assert.equal($('link').length, 1); }); it('Dynamic API routes work', async () => { - const json = await fetchJSON('/api/products/33'); + const json = await fetchJSON('/api/products/33/'); assert.equal(json.id, '33'); }); it('Injected route work', async () => { - const json = await fetchJSON('/path-alias/33'); + const json = await fetchJSON('/path-alias/33/'); assert.equal(json.id, '33'); }); @@ -98,7 +98,7 @@ describe('Dynamic pages in SSR', () => { }); it('injectRoute entrypoint should not fail build if containing the extension several times in the path', async () => { - const html = await fetchHTML('/test'); + const html = await fetchHTML('/test/'); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Index'); }); diff --git a/packages/astro/test/ssr-env.test.js b/packages/astro/test/ssr-env.test.js index 7da190736768..71fa9eb75c00 100644 --- a/packages/astro/test/ssr-env.test.js +++ b/packages/astro/test/ssr-env.test.js @@ -19,7 +19,7 @@ describe('SSR Environment Variables', () => { it('import.meta.env.SSR is true', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/ssr'); + const request = new Request('http://example.com/ssr/'); const response = await app.render(request); const html = await response.text(); const $ = cheerio.load(html); diff --git a/packages/astro/test/ssr-error-pages.test.js b/packages/astro/test/ssr-error-pages.test.js index f62c507b53d8..b3af8fc3b0d1 100644 --- a/packages/astro/test/ssr-error-pages.test.js +++ b/packages/astro/test/ssr-error-pages.test.js @@ -79,7 +79,7 @@ describe('404 and 500 pages', () => { }); it('404 page returned when there is an 404 response returned from route', async () => { - const request = new Request('http://example.com/causes-404'); + const request = new Request('http://example.com/causes-404/'); const response = await app.render(request); assert.equal(response.status, 404); const html = await response.text(); @@ -88,7 +88,7 @@ describe('404 and 500 pages', () => { }); it('500 page returned when there is an error', async () => { - const request = new Request('http://example.com/causes-error'); + const request = new Request('http://example.com/causes-error/'); const response = await app.render(request); assert.equal(response.status, 500); const html = await response.text(); @@ -97,7 +97,7 @@ describe('404 and 500 pages', () => { }); it('Returns 404 when hitting an API route with the wrong method', async () => { - const request = new Request('http://example.com/api/route', { + const request = new Request('http://example.com/api/route/', { method: 'PUT', }); const response = await app.render(request); @@ -108,56 +108,3 @@ describe('404 and 500 pages', () => { }); }); }); - -describe('trailing slashes for error pages', () => { - /** @type {import('./test-utils.js').Fixture} */ - let fixture; - - before(async () => { - fixture = await loadFixture({ - root: './fixtures/ssr-error-pages/', - output: 'server', - adapter: testAdapter(), - trailingSlash: 'always', - }); - }); - - describe('Development', () => { - /** @type {import('./test-utils.js').DevServer} */ - let devServer; - - before(async () => { - devServer = await fixture.startDevServer(); - }); - - after(async () => { - await devServer.stop(); - }); - - it('renders 404 page when a route does not match the request', async () => { - const response = await fixture.fetch('/ashbfjkasn'); - assert.equal(response.status, 404); - const html = await response.text(); - const $ = cheerio.load(html); - assert.equal($('h1').text(), `Something went horribly wrong!`); - }); - }); - - describe('Production', () => { - /** @type {import('./test-utils.js').App} */ - let app; - - before(async () => { - await fixture.build({}); - app = await fixture.loadTestAdapterApp(); - }); - - it('renders 404 page when a route does not match the request', async () => { - const response = await app.render(new Request('http://example.com/ajksalscla')); - assert.equal(response.status, 404); - const html = await response.text(); - const $ = cheerio.load(html); - assert.equal($('h1').text(), 'Something went horribly wrong!'); - }); - }); -}); diff --git a/packages/astro/test/ssr-locals.test.js b/packages/astro/test/ssr-locals.test.js index 3adb2a8cd67e..cd64b5920e16 100644 --- a/packages/astro/test/ssr-locals.test.js +++ b/packages/astro/test/ssr-locals.test.js @@ -31,7 +31,7 @@ describe('SSR Astro.locals from server', () => { }); it('Can access Astro.locals in api context', async () => { - const request = new Request('http://example.com/api'); + const request = new Request('http://example.com/api/'); const locals = { foo: 'bar' }; const response = await app.render(request, { routeData: undefined, locals }); assert.equal(response.status, 200); @@ -40,7 +40,7 @@ describe('SSR Astro.locals from server', () => { assert.equal(body.foo, 'bar'); }); - it('404.astro can access locals provided to app.render()', async () => { + it('404.astro can access locals provided to app.rendDer()', async () => { const request = new Request('http://example.com/slkfnasf'); const locals = { foo: 'par' }; const response = await app.render(request, { locals }); @@ -52,7 +52,7 @@ describe('SSR Astro.locals from server', () => { }); it('500.astro can access locals provided to app.render()', async () => { - const request = new Request('http://example.com/go-to-error-page'); + const request = new Request('http://example.com/go-to-error-page/'); const locals = { foo: 'par' }; const response = await app.render(request, { locals }); assert.equal(response.status, 500); diff --git a/packages/astro/test/ssr-markdown.test.js b/packages/astro/test/ssr-markdown.test.js index 286891790316..e7f1d1ba7ac6 100644 --- a/packages/astro/test/ssr-markdown.test.js +++ b/packages/astro/test/ssr-markdown.test.js @@ -26,7 +26,7 @@ describe('Markdown pages in SSR', () => { } it('Renders markdown pages correctly', async () => { - const html = await fetchHTML('/post'); + const html = await fetchHTML('/post/'); const $ = cheerioLoad(html); assert.equal($('#subheading').text(), 'Subheading'); }); diff --git a/packages/astro/test/ssr-params.test.js b/packages/astro/test/ssr-params.test.js index 13c071e7d0f4..042a6fb624d4 100644 --- a/packages/astro/test/ssr-params.test.js +++ b/packages/astro/test/ssr-params.test.js @@ -20,7 +20,7 @@ describe('Astro.params in SSR', () => { it('Params are passed to component', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/users/houston/food'); + const request = new Request('http://example.com/users/houston/food/'); const response = await app.render(request); assert.equal(response.status, 200); const html = await response.text(); @@ -31,7 +31,7 @@ describe('Astro.params in SSR', () => { describe('Non-english characters in the URL', () => { it('Params are passed to component', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/users/houston/東西/food'); + const request = new Request('http://example.com/users/houston/東西/food/'); const response = await app.render(request); assert.equal(response.status, 200); const html = await response.text(); diff --git a/packages/astro/test/ssr-prerender-get-static-paths.test.js b/packages/astro/test/ssr-prerender-get-static-paths.test.js index 50b403891d11..29ae4513b7a6 100644 --- a/packages/astro/test/ssr-prerender-get-static-paths.test.js +++ b/packages/astro/test/ssr-prerender-get-static-paths.test.js @@ -78,24 +78,24 @@ describe('Prerender', () => { describe('404 behavior', () => { it('resolves 200 on matching static path - named params', async () => { - const res = await fixture.fetch('/blog/pizza/provolone-sausage'); + const res = await fixture.fetch('/blog/pizza/provolone-sausage/'); assert.equal(res.status, 200); }); it('resolves 404 on pattern match without static path - named params', async () => { - const res = await fixture.fetch('/blog/pizza/provolone-pineapple'); + const res = await fixture.fetch('/blog/pizza/provolone-pineapple/'); const html = await res.text(); assert.equal(res.status, 404); assert.match(html, /404/); }); it('resolves 200 on matching static path - rest params', async () => { - const res = await fixture.fetch('/blog/pizza/grimaldis/new-york'); + const res = await fixture.fetch('/blog/pizza/grimaldis/new-york/'); assert.equal(res.status, 200); }); it('resolves 404 on pattern match without static path - rest params', async () => { - const res = await fixture.fetch('/blog/pizza/pizza-hut'); + const res = await fixture.fetch('/blog/pizza/pizza-hut/'); const html = await res.text(); assert.equal(res.status, 404); @@ -106,21 +106,21 @@ describe('Prerender', () => { describe('route params type validation', () => { it('resolves 200 on matching static path - string params', async () => { // route provided with { params: { year: "2022", slug: "post-2" }} - const res = await fixture.fetch('/blog/blog/2022/post-1'); + const res = await fixture.fetch('/blog/blog/2022/post-1/'); assert.equal(res.status, 200); }); it('resolves 200 on matching static path - numeric params', async () => { // route provided with { params: { year: 2022, slug: "post-2" }} - const res = await fixture.fetch('/blog/blog/2022/post-2'); + const res = await fixture.fetch('/blog/blog/2022/post-2/'); assert.equal(res.status, 200); }); }); it('resolves 200 on matching static paths', async () => { - // routes params provided for pages /posts/1, /posts/2, and /posts/3 + // routes params provided for pages /posts/1/, /posts/2/, and /posts/3/ for (const page of [1, 2, 3]) { - let res = await fixture.fetch(`/blog/posts/${page}`); + let res = await fixture.fetch(`/blog/posts/${page}/`); assert.equal(res.status, 200); const html = await res.text(); @@ -206,24 +206,24 @@ describe('Prerender', () => { describe('404 behavior', () => { it('resolves 200 on matching static path - named params', async () => { - const res = await fixture.fetch('/blog/pizza/provolone-sausage'); + const res = await fixture.fetch('/blog/pizza/provolone-sausage/'); assert.equal(res.status, 200); }); it('resolves 404 on pattern match without static path - named params', async () => { - const res = await fixture.fetch('/blog/pizza/provolone-pineapple'); + const res = await fixture.fetch('/blog/pizza/provolone-pineapple/'); const html = await res.text(); assert.equal(res.status, 404); assert.match(html, /404/); }); it('resolves 200 on matching static path - rest params', async () => { - const res = await fixture.fetch('/blog/pizza/grimaldis/new-york'); + const res = await fixture.fetch('/blog/pizza/grimaldis/new-york/'); assert.equal(res.status, 200); }); it('resolves 404 on pattern match without static path - rest params', async () => { - const res = await fixture.fetch('/blog/pizza/pizza-hut'); + const res = await fixture.fetch('/blog/pizza/pizza-hut/'); const html = await res.text(); assert.equal(res.status, 404); @@ -234,21 +234,21 @@ describe('Prerender', () => { describe('route params type validation', () => { it('resolves 200 on matching static path - string params', async () => { // route provided with { params: { year: "2022", slug: "post-2" }} - const res = await fixture.fetch('/blog/blog/2022/post-1'); + const res = await fixture.fetch('/blog/blog/2022/post-1/'); assert.equal(res.status, 200); }); it('resolves 200 on matching static path - numeric params', async () => { // route provided with { params: { year: 2022, slug: "post-2" }} - const res = await fixture.fetch('/blog/blog/2022/post-2'); + const res = await fixture.fetch('/blog/blog/2022/post-2/'); assert.equal(res.status, 200); }); }); it('resolves 200 on matching static paths', async () => { - // routes params provided for pages /posts/1, /posts/2, and /posts/3 + // routes params provided for pages /posts/1/, /posts/2/, and /posts/3/ for (const page of [1, 2, 3]) { - let res = await fixture.fetch(`/blog/posts/${page}`); + let res = await fixture.fetch(`/blog/posts/${page}/`); assert.equal(res.status, 200); const html = await res.text(); diff --git a/packages/astro/test/ssr-prerender.test.js b/packages/astro/test/ssr-prerender.test.js index a1620d752d60..80e982c9c13b 100644 --- a/packages/astro/test/ssr-prerender.test.js +++ b/packages/astro/test/ssr-prerender.test.js @@ -56,7 +56,7 @@ describe('SSR: prerender', () => { // bug id #6020 it('fix bug id #6020', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/some'); + const request = new Request('http://example.com/some/'); const response = await app.render(request); assert.equal(response.status, 200); const html = await response.text(); diff --git a/packages/astro/test/ssr-request.test.js b/packages/astro/test/ssr-request.test.js index a520b350cceb..8d0cc57b742c 100644 --- a/packages/astro/test/ssr-request.test.js +++ b/packages/astro/test/ssr-request.test.js @@ -35,7 +35,7 @@ describe('Using Astro.request in SSR', () => { it('Gets the request passed in', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/subpath/request'); + const request = new Request('http://example.com/subpath/request/'); const response = await app.render(request); assert.equal(response.status, 200); const html = await response.text(); @@ -50,7 +50,7 @@ describe('Using Astro.request in SSR', () => { it('CSS assets have their base prefix', async () => { const app = await fixture.loadTestAdapterApp(); - let request = new Request('http://example.com/subpath/request'); + let request = new Request('http://example.com/subpath/request/'); let response = await app.render(request); assert.equal(response.status, 200); const html = await response.text(); @@ -69,7 +69,7 @@ describe('Using Astro.request in SSR', () => { it('script assets have their base prefix', async () => { const app = await fixture.loadTestAdapterApp(); - let request = new Request('http://example.com/subpath/request'); + let request = new Request('http://example.com/subpath/request/'); let response = await app.render(request); assert.equal(response.status, 200); const html = await response.text(); diff --git a/packages/astro/test/ssr-response.test.js b/packages/astro/test/ssr-response.test.js index c3ddb2744f09..76c8d7c2f36f 100644 --- a/packages/astro/test/ssr-response.test.js +++ b/packages/astro/test/ssr-response.test.js @@ -18,21 +18,21 @@ describe('Using Astro.response in SSR', () => { it('Can set the status', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/status-code'); + const request = new Request('http://example.com/status-code/'); const response = await app.render(request); assert.equal(response.status, 404); }); it('Can set the statusText', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/status-code'); + const request = new Request('http://example.com/status-code/'); const response = await app.render(request); assert.equal(response.statusText, 'Oops'); }); it('Can set headers for 404 page', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/status-code'); + const request = new Request('http://example.com/status-code/'); const response = await app.render(request); const headers = response.headers; assert.equal(headers.get('one-two'), 'three'); @@ -40,7 +40,7 @@ describe('Using Astro.response in SSR', () => { it('Can add headers', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/some-header'); + const request = new Request('http://example.com/some-header/'); const response = await app.render(request); const headers = response.headers; assert.equal(headers.get('one-two'), 'three'); diff --git a/packages/astro/test/static-build.test.js b/packages/astro/test/static-build.test.js index c16563ac83f7..dd4a2eb4a343 100644 --- a/packages/astro/test/static-build.test.js +++ b/packages/astro/test/static-build.test.js @@ -60,7 +60,7 @@ describe('Static build', () => { const $ = cheerioLoad(html); const link = $('.posts a'); const href = link.attr('href'); - assert.equal(href, '/subpath/posts/thoughts'); + assert.equal(href, '/subpath/posts/thoughts/'); }); it('Builds out .md pages', async () => { diff --git a/packages/astro/test/streaming.test.js b/packages/astro/test/streaming.test.js index cbd4fa4f4b46..7bdffd7d7e17 100644 --- a/packages/astro/test/streaming.test.js +++ b/packages/astro/test/streaming.test.js @@ -41,7 +41,7 @@ describe('Streaming', () => { }); it('Body of slots is chunked', async () => { - let res = await fixture.fetch('/slot'); + let res = await fixture.fetch('/slot/'); let chunks = []; for await (const bytes of streamAsyncIterator(res.body)) { let chunk = decoder.decode(bytes); @@ -81,7 +81,7 @@ describe('Streaming', () => { // if the offshoot promise goes unhandled, this test will pass immediately but fail the test suite it('Stays alive on failed component renders initiated by failed render templates', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/multiple-errors'); + const request = new Request('http://example.com/multiple-errors/'); const response = await app.render(request); assert.equal(response.status, 500); const text = await response.text(); diff --git a/packages/astro/test/units/dev/dev.test.js b/packages/astro/test/units/dev/dev.test.js index c82232768aba..543dfc80460e 100644 --- a/packages/astro/test/units/dev/dev.test.js +++ b/packages/astro/test/units/dev/dev.test.js @@ -145,7 +145,7 @@ describe('dev container', () => { async (container) => { let r = createRequestAndResponse({ method: 'GET', - url: '/test-one', + url: '/test-one/', }); container.handle(r.req, r.res); await r.done; @@ -154,7 +154,7 @@ describe('dev container', () => { // Try with the injected route r = createRequestAndResponse({ method: 'GET', - url: '/another-two', + url: '/another-two/', }); container.handle(r.req, r.res); await r.done; @@ -196,7 +196,7 @@ describe('dev container', () => { async (container) => { { // Regular pages are served as expected. - const r = createRequestAndResponse({ method: 'GET', url: '/page' }); + const r = createRequestAndResponse({ method: 'GET', url: '/page/' }); container.handle(r.req, r.res); await r.done; const doc = await r.text(); diff --git a/packages/astro/test/units/routing/endpoints.test.js b/packages/astro/test/units/routing/endpoints.test.js index 43d76af5a57d..0c58f2bcc1eb 100644 --- a/packages/astro/test/units/routing/endpoints.test.js +++ b/packages/astro/test/units/routing/endpoints.test.js @@ -43,7 +43,7 @@ describe('endpoints', () => { it('should return a redirect response with location header', async () => { const { req, res, done } = createRequestAndResponse({ method: 'GET', - url: '/response-redirect', + url: '/response-redirect/', }); container.handle(req, res); await done; @@ -56,7 +56,7 @@ describe('endpoints', () => { it('should return a response with location header', async () => { const { req, res, done } = createRequestAndResponse({ method: 'GET', - url: '/response', + url: '/response/', }); container.handle(req, res); await done; @@ -80,7 +80,7 @@ describe('endpoints', () => { it('should remove internally-used header for HTTP status 500', async () => { const { req, res, done } = createRequestAndResponse({ method: 'GET', - url: '/internal-error', + url: '/internal-error/', }); container.handle(req, res); await done; diff --git a/packages/astro/test/units/runtime/endpoints.test.js b/packages/astro/test/units/runtime/endpoints.test.js index 47f52a2dff72..c8a87cf54822 100644 --- a/packages/astro/test/units/runtime/endpoints.test.js +++ b/packages/astro/test/units/runtime/endpoints.test.js @@ -40,7 +40,7 @@ describe('endpoints', () => { it('should respond with 500 for incorrect implementation', async () => { const { req, res, done } = createRequestAndResponse({ method: 'GET', - url: '/incorrect', + url: '/incorrect/', }); container.handle(req, res); await done; 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 26f446cb1d27..1da85d54d455 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 @@ -110,21 +110,21 @@ describe('vite-plugin-astro-server', () => { it('params are included', async () => { const { req, res, text } = createRequestAndResponse({ method: 'GET', - url: '/url?xyz=123', + url: '/url/?xyz=123', }); container.handle(req, res); const html = await text(); - assert.deepEqual(html, 'http://undefined/url?xyz=123'); + assert.deepEqual(html, 'http://undefined/url/?xyz=123'); }); it('params are excluded on prerendered routes', async () => { const { req, res, text } = createRequestAndResponse({ method: 'GET', - url: '/prerendered?xyz=123', + url: '/prerendered/?xyz=123', }); container.handle(req, res); const html = await text(); - assert.deepEqual(html, 'http://undefined/prerendered'); + assert.deepEqual(html, 'http://undefined/prerendered/'); }); }); }); 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 fadaed99f4c2..f95a6f3fdd72 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 @@ -87,7 +87,7 @@ describe('endpoints', () => { it('Can bail on streaming', async () => { const { req, res, done } = createRequestAndResponse({ method: 'GET', - url: '/streaming', + url: '/streaming/', }); const locals = { cancelledByTheServer: false }; diff --git a/packages/astro/test/view-transitions.test.js b/packages/astro/test/view-transitions.test.js index 168d25f8e5c0..176b7ba6c757 100644 --- a/packages/astro/test/view-transitions.test.js +++ b/packages/astro/test/view-transitions.test.js @@ -17,7 +17,7 @@ describe('View Transitions styles', () => { }); it('style tag added for each instance of the component', async () => { - let res = await fixture.fetch('/multiple'); + let res = await fixture.fetch('/multiple/'); let html = await res.text(); let $ = cheerio.load(html); @@ -25,7 +25,7 @@ describe('View Transitions styles', () => { }); it('should not duplicate transition attributes on island contents', async () => { - let res = await fixture.fetch('/hasIsland'); + let res = await fixture.fetch('/hasIsland/'); let html = await res.text(); let $ = cheerio.load(html); assert.equal($('astro-island[data-astro-transition-persist]').length, 1); From 5dc9923e846b2a3f0e72346081bfedd21adff387 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 10 Sep 2024 17:05:05 +0800 Subject: [PATCH 3/4] Handle trailing slash 301 redirect --- packages/astro/src/core/messages.ts | 3 + .../src/core/preview/static-preview-server.ts | 2 +- .../core/preview/vite-plugin-astro-preview.ts | 60 +------------ .../src/vite-plugin-astro-server/plugin.ts | 4 + packages/astro/src/vite-plugin-utils/index.ts | 4 +- .../trailing-slash-middleware.ts | 84 +++++++++++++++++++ 6 files changed, 99 insertions(+), 58 deletions(-) create mode 100644 packages/astro/src/vite-plugin-utils/trailing-slash-middleware.ts diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts index 8104d993b121..433a953cea4c 100644 --- a/packages/astro/src/core/messages.ts +++ b/packages/astro/src/core/messages.ts @@ -37,12 +37,14 @@ export function req({ statusCode, reqTime, isRewrite, + redirectLocation, }: { url: string; statusCode: number; method?: string; reqTime?: number; isRewrite?: boolean; + redirectLocation?: string; }): string { const color = statusCode >= 500 ? red : statusCode >= 300 ? yellow : blue; return ( @@ -51,6 +53,7 @@ export function req({ `${isRewrite ? color('(rewrite) ') : ''}` + (method && method !== 'GET' ? color(method) + ' ' : '') + url + + (statusCode >= 300 && statusCode < 400 && redirectLocation ? ` → ${redirectLocation}` : '') + ` ` + (reqTime ? dim(Math.round(reqTime) + 'ms') : '') ); diff --git a/packages/astro/src/core/preview/static-preview-server.ts b/packages/astro/src/core/preview/static-preview-server.ts index 855506ef91ce..5508e0c756f3 100644 --- a/packages/astro/src/core/preview/static-preview-server.ts +++ b/packages/astro/src/core/preview/static-preview-server.ts @@ -37,7 +37,7 @@ export default async function createStaticPreviewServer( headers: settings.config.server.headers, open: settings.config.server.open, }, - plugins: [vitePluginAstroPreview(settings)], + plugins: [vitePluginAstroPreview(settings, logger)], }); } catch (err) { if (err instanceof Error) { diff --git a/packages/astro/src/core/preview/vite-plugin-astro-preview.ts b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts index fd9bbae66c57..c77748e462d2 100644 --- a/packages/astro/src/core/preview/vite-plugin-astro-preview.ts +++ b/packages/astro/src/core/preview/vite-plugin-astro-preview.ts @@ -4,12 +4,10 @@ import { fileURLToPath } from 'node:url'; import type { Connect, Plugin } from 'vite'; import { notFoundTemplate, subpathNotUsedTemplate } from '../../template/4xx.js'; import type { AstroSettings } from '../../types/astro.js'; -import { cleanUrl } from '../../vite-plugin-utils/index.js'; -import { stripBase } from './util.js'; +import { trailingSlashMiddleware } from '../../vite-plugin-utils/index.js'; +import type { Logger } from '../logger/core.js'; -const HAS_FILE_EXTENSION_REGEXP = /\.[^/]+$/; - -export function vitePluginAstroPreview(settings: AstroSettings): Plugin { +export function vitePluginAstroPreview(settings: AstroSettings, logger: Logger): Plugin { const { base, outDir, trailingSlash } = settings.config; function handle404(req: IncomingMessage, res: ServerResponse) { @@ -36,30 +34,6 @@ export function vitePluginAstroPreview(settings: AstroSettings): Plugin { return; } - const pathname = cleanUrl(stripBase(req.url!, base)); - const isRoot = pathname === '/'; - - // Validate trailingSlash - if (!isRoot) { - const hasTrailingSlash = pathname.endsWith('/'); - - if (hasTrailingSlash && trailingSlash == 'never') { - res.statusCode = 404; - res.end(notFoundTemplate(pathname, 'Not Found (trailingSlash is set to "never")')); - return; - } - - if ( - !hasTrailingSlash && - trailingSlash == 'always' && - !HAS_FILE_EXTENSION_REGEXP.test(pathname) - ) { - res.statusCode = 404; - res.end(notFoundTemplate(pathname, 'Not Found (trailingSlash is set to "always")')); - return; - } - } - // TODO: look into why the replacement needs to happen here for (const middleware of server.middlewares.stack) { // This hardcoded name will not break between Vite versions @@ -71,33 +45,7 @@ export function vitePluginAstroPreview(settings: AstroSettings): Plugin { next(); }); - return () => { - // NOTE: the `base` is stripped from `req.url` for post middlewares - - server.middlewares.use((req, res, next) => { - const pathname = cleanUrl(req.url!); - - // Vite doesn't handle /foo/ if /foo.html exists, we handle it anyways - if (pathname.endsWith('/')) { - const pathnameWithoutSlash = pathname.slice(0, -1); - const htmlPath = fileURLToPath(outDir + pathnameWithoutSlash + '.html'); - if (fs.existsSync(htmlPath)) { - req.url = pathnameWithoutSlash + '.html'; - return next(); - } - } - // Vite doesn't handle /foo if /foo/index.html exists, we handle it anyways - else { - const htmlPath = fileURLToPath(outDir + pathname + '/index.html'); - if (fs.existsSync(htmlPath)) { - req.url = pathname + '/index.html'; - return next(); - } - } - - next(); - }); - }; + server.middlewares.use(trailingSlashMiddleware(trailingSlash, logger)); }, }; } diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index f1cfa16ba33a..bebb46f30482 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -19,6 +19,7 @@ import { recordServerError } from './error.js'; import { DevPipeline } from './pipeline.js'; import { handleRequest } from './request.js'; import { setRouteError } from './server-state.js'; +import { trailingSlashMiddleware } from '../vite-plugin-utils/trailing-slash-middleware.js'; export interface AstroPluginOptions { settings: AstroSettings; @@ -84,6 +85,9 @@ export default function createVitePluginAstroServer({ route: '', handle: baseMiddleware(settings, logger), }); + + viteServer.middlewares.use(trailingSlashMiddleware(settings.config.trailingSlash, logger)); + // Note that this function has a name so other middleware can find it. viteServer.middlewares.use(async function astroDevHandler(request, response) { if (request.url === undefined || !request.method) { diff --git a/packages/astro/src/vite-plugin-utils/index.ts b/packages/astro/src/vite-plugin-utils/index.ts index 2bf398a78249..8f8a5ca3abb5 100644 --- a/packages/astro/src/vite-plugin-utils/index.ts +++ b/packages/astro/src/vite-plugin-utils/index.ts @@ -1,5 +1,5 @@ import { fileURLToPath } from 'node:url'; -import path from 'node:path' +import path from 'node:path'; import ancestor from 'common-ancestor-path'; import { appendExtension, @@ -9,6 +9,8 @@ import { import { viteID } from '../core/util.js'; import type { AstroConfig } from '../types/public/config.js'; +export { trailingSlashMiddleware } from './trailing-slash-middleware.js'; + export function getFileInfo(id: string, config: AstroConfig) { const sitePathname = appendForwardSlash( config.site ? new URL(config.base, config.site).pathname : config.base, diff --git a/packages/astro/src/vite-plugin-utils/trailing-slash-middleware.ts b/packages/astro/src/vite-plugin-utils/trailing-slash-middleware.ts new file mode 100644 index 000000000000..fb733de1adbe --- /dev/null +++ b/packages/astro/src/vite-plugin-utils/trailing-slash-middleware.ts @@ -0,0 +1,84 @@ +import { bold } from 'kleur/colors'; +import type { Connect } from 'vite'; +import { cleanUrl } from './index.js'; +import * as messages from '../core/messages.js'; +import type { Logger } from '../core/logger/core.js'; + +const HAS_FILE_EXTENSION_REGEXP = /\.[^/]+$/; + +export function trailingSlashMiddleware( + trailingSlash: 'never' | 'always' | 'ignore', + logger: Logger, +): Connect.NextHandleFunction { + if (trailingSlash === 'ignore') { + return (req, res, next) => { + next(); + }; + } + + return (req, res, next) => { + if (req.url == null) { + return next(); + } + + const pathname = cleanUrl(req.url); + if (pathname === '/') { + return next(); + } + + const hasTrailingSlash = pathname.endsWith('/'); + + if (trailingSlash === 'never' && hasTrailingSlash) { + const correctUrl = pathname.slice(0, -1) + req.url.slice(pathname.length); + logRedirect(logger, req, correctUrl); + res.writeHead(301, { Location: correctUrl }); + res.end(); + return; + } + + if ( + trailingSlash === 'always' && + !hasTrailingSlash && + !HAS_FILE_EXTENSION_REGEXP.test(pathname) + ) { + const correctUrl = pathname + '/' + req.url.slice(pathname.length); + logRedirect(logger, req, correctUrl); + res.writeHead(301, { Location: correctUrl }); + res.end(); + return; + } + + next(); + }; +} + +function logRedirect(logger: Logger, req: Connect.IncomingMessage, correctUrl: string) { + logger.info( + null, + messages.req({ + url: req.url!, // `req.url` is already checked before calling this function + method: req.method, + statusCode: 301, + redirectLocation: correctUrl, + }), + ); + + // If referer is provided, we try to provide a helpful warning if the user is accessing a page + // from an incorrect trailing slash. This prevents potentially reaching incorrect URLs in production + // since some hosts are lenient with trailing slashes and could serve anyway (which it really shouldn't), + // leading to more issues for users that shouldn't need fixing in the first place. + const referrer = req.headers.referer; + if (referrer && referrer.includes('://localhost')) { + try { + const referrerUrl = new URL(referrer); + logger.warn( + 'router', + `${bold(referrerUrl.pathname + referrerUrl.search)} has a link to ${bold(req.url!)}, but it redirected to ${bold(correctUrl)}. ` + + `Some hosts may not redirect automatically, or worse, serve the page anyway, which can lead to issues with relative links ` + + `on your page in production only. To prevent this, make sure the page links to ${bold(correctUrl)} directly instead.`, + ); + } catch { + // Ignore URL parse errors + } + } +} From 9440cb482e1b028d83255d07739ff62df2a238d9 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 10 Sep 2024 22:13:22 +0800 Subject: [PATCH 4/4] Fix images and tests --- packages/astro/src/assets/endpoint/config.ts | 12 +++++--- packages/astro/src/assets/services/service.ts | 5 +++- .../astro/src/assets/vite-plugin-assets.ts | 2 +- packages/astro/test/actions.test.js | 8 ++--- packages/astro/test/astro-dev-http2.test.js | 1 - .../astro/test/astro-get-static-paths.test.js | 7 ++--- packages/astro/test/astro-global.test.js | 2 +- .../astro/test/astro-markdown-url.test.js | 4 +-- packages/astro/test/astro-markdown.test.js | 2 +- .../test/astro-pagination-root-spread.test.js | 4 +-- packages/astro/test/astro-pagination.test.js | 4 +-- .../test/content-collections-render.test.js | 4 +-- packages/astro/test/core-image.test.js | 29 ++++++++++--------- .../astro/test/custom-404-injected.test.js | 2 +- packages/astro/test/custom-404-static.test.js | 2 +- .../experimental-content-collections.test.js | 2 +- .../astro-cookies/src/pages/from-endpoint.ts | 2 +- packages/astro/test/i18n-routing.test.js | 4 +-- .../test/units/routing/trailing-slash.test.js | 1 + 19 files changed, 52 insertions(+), 45 deletions(-) diff --git a/packages/astro/src/assets/endpoint/config.ts b/packages/astro/src/assets/endpoint/config.ts index 382645d6ee80..53d21cdd9e91 100644 --- a/packages/astro/src/assets/endpoint/config.ts +++ b/packages/astro/src/assets/endpoint/config.ts @@ -1,4 +1,5 @@ import { resolveInjectedRoute } from '../../core/routing/manifest/create.js'; +import { getPattern } from '../../core/routing/manifest/pattern.js'; import type { AstroSettings, ManifestData } from '../../types/astro.js'; import type { RouteData } from '../../types/public/internal.js'; @@ -31,16 +32,19 @@ function getImageEndpointData( settings.config.image.endpoint ?? (mode === 'dev' ? 'astro/assets/endpoint/node' : 'astro/assets/endpoint/generic'); + const segments = [[{ content: '_image', dynamic: false, spread: false }]]; + const trailing = settings.config.trailingSlash === 'always' ? '/' : ''; + return { type: 'endpoint', isIndex: false, - route: '/_image', - pattern: /^\/_image$/, - segments: [[{ content: '_image', dynamic: false, spread: false }]], + route: '/_image' + trailing, + pattern: getPattern(segments, settings.config.base, settings.config.trailingSlash), + segments, params: [], component: resolveInjectedRoute(endpointEntrypoint, settings.config.root, cwd).component, generate: () => '', - pathname: '/_image', + pathname: '/_image' + trailing, prerender: false, fallbackRoutes: [], }; diff --git a/packages/astro/src/assets/services/service.ts b/packages/astro/src/assets/services/service.ts index f798e1dfd142..d5d4598a9be9 100644 --- a/packages/astro/src/assets/services/service.ts +++ b/packages/astro/src/assets/services/service.ts @@ -343,8 +343,11 @@ export const baseService: Omit = { options[key] && searchParams.append(param, options[key].toString()); }); + // @ts-expect-error temporary hack. make sure Bjorn fixes this or else + const trailing = imageConfig.__trailingSlash === 'always' ? '/' : ''; + const imageEndpoint = joinPaths(import.meta.env.BASE_URL, '/_image'); - return `${imageEndpoint}?${searchParams}`; + return `${imageEndpoint}${trailing}?${searchParams}`; }, parseURL(url) { const params = url.searchParams; diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts index 5c40d98b4f8e..df067850ad43 100644 --- a/packages/astro/src/assets/vite-plugin-assets.ts +++ b/packages/astro/src/assets/vite-plugin-assets.ts @@ -121,7 +121,7 @@ export default function assets({ export { default as Picture } from "astro/components/Picture.astro"; export { inferRemoteSize } from "astro/assets/utils/inferRemoteSize.js"; - export const imageConfig = ${JSON.stringify(settings.config.image)}; + export const imageConfig = ${JSON.stringify({ ...settings.config.image, __trailingSlash: settings.config.trailingSlash })}; // This is used by the @astrojs/node integration to locate images. // It's unused on other platforms, but on some platforms like Netlify (and presumably also Vercel) // new URL("dist/...") is interpreted by the bundler as a signal to include that directory diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js index 5446226cc3d0..3fd954109772 100644 --- a/packages/astro/test/actions.test.js +++ b/packages/astro/test/actions.test.js @@ -50,7 +50,7 @@ describe('Astro Actions', () => { }); it('Exposes subscribe action', async () => { - const res = await fixture.fetch('/_actions/subscribe', { + const res = await fixture.fetch('/_actions/subscribe/', { method: 'POST', body: JSON.stringify({ channel: 'bholmesdev' }), headers: { @@ -70,7 +70,7 @@ describe('Astro Actions', () => { const formData = new FormData(); formData.append('channel', 'bholmesdev'); formData.append('comment', 'Hello, World!'); - const res = await fixture.fetch('/_actions/comment', { + const res = await fixture.fetch('/_actions/comment/', { method: 'POST', body: formData, }); @@ -86,7 +86,7 @@ describe('Astro Actions', () => { it('Raises validation error on bad form data', async () => { const formData = new FormData(); formData.append('channel', 'bholmesdev'); - const res = await fixture.fetch('/_actions/comment', { + const res = await fixture.fetch('/_actions/comment/', { method: 'POST', body: formData, }); @@ -103,7 +103,7 @@ describe('Astro Actions', () => { const formData = new FormData(); formData.append('channel', 'bholmesdev'); formData.append('comment', 'Hello, World!'); - const res = await fixture.fetch('/_actions/commentPlainFormData', { + const res = await fixture.fetch('/_actions/commentPlainFormData/', { method: 'POST', body: formData, }); diff --git a/packages/astro/test/astro-dev-http2.test.js b/packages/astro/test/astro-dev-http2.test.js index 10521b8cb09c..a4ea53b3ed70 100644 --- a/packages/astro/test/astro-dev-http2.test.js +++ b/packages/astro/test/astro-dev-http2.test.js @@ -23,7 +23,6 @@ describe('Astro HTTP/2 support', () => { const result = await fixture.fetch('/'); assert.equal(result.status, 200); const html = await result.text(); - console.log(result.headers); const $ = cheerio.load(html); const urlString = $('main').text(); assert.equal(Boolean(urlString), true); diff --git a/packages/astro/test/astro-get-static-paths.test.js b/packages/astro/test/astro-get-static-paths.test.js index 170391ba8a09..0a142c95dbbb 100644 --- a/packages/astro/test/astro-get-static-paths.test.js +++ b/packages/astro/test/astro-get-static-paths.test.js @@ -11,7 +11,6 @@ describe('getStaticPaths - build calls', () => { fixture = await loadFixture({ root: './fixtures/astro-get-static-paths/', site: 'https://mysite.dev/', - trailingSlash: 'never', base: '/blog', }); await fixture.build(); @@ -31,7 +30,7 @@ describe('getStaticPaths - build calls', () => { const html = await fixture.readFile('/food/tacos/index.html'); const $ = cheerio.load(html); - assert.equal($('#url').text(), '/blog/food/tacos'); + assert.equal($('#url').text(), '/blog/food/tacos/'); }); }); @@ -115,8 +114,8 @@ describe('getStaticPaths - dev calls', () => { const canonical = $('link[rel=canonical]'); assert.equal( canonical.attr('href'), - `https://mysite.dev/posts/${page}`, - `doesn't trim the /${page} route param`, + `https://mysite.dev/posts/${page}/`, + `doesn't trim the /${page}/ route param`, ); } }); diff --git a/packages/astro/test/astro-global.test.js b/packages/astro/test/astro-global.test.js index 8ad79104a916..c37e2bac629a 100644 --- a/packages/astro/test/astro-global.test.js +++ b/packages/astro/test/astro-global.test.js @@ -169,7 +169,7 @@ describe('Astro Global', () => { let $ = cheerio.load(html); assert.match($('#pattern').text(), /Astro route pattern: \//); assert.match($('#pattern-middleware').text(), /Astro route pattern middleware: \//); - response = await app.render(new Request('https://example.com/omit-markdown-extensions')); + response = await app.render(new Request('https://example.com/omit-markdown-extensions/')); html = await response.text(); $ = cheerio.load(html); assert.match($('#pattern').text(), /Astro route pattern: \/omit-markdown-extensions/); diff --git a/packages/astro/test/astro-markdown-url.test.js b/packages/astro/test/astro-markdown-url.test.js index 9add78299c3a..423779c28b99 100644 --- a/packages/astro/test/astro-markdown-url.test.js +++ b/packages/astro/test/astro-markdown-url.test.js @@ -34,7 +34,7 @@ describe('Astro Markdown URL', () => { const html = await fixture.readFile('/index.html'); const $ = cheerio.load(html); - assert.equal($('#url').attr('href'), baseUrl); + assert.equal($('#url').attr('href'), baseUrl + '.html'); }); it('trailingSlash: ignore', async () => { @@ -81,7 +81,7 @@ describe('Astro Markdown URL', () => { const html = await fixture.readFile('/index.html'); const $ = cheerio.load(html); - assert.equal($('#url').attr('href'), baseUrl); + assert.equal($('#url').attr('href'), baseUrl + '.html'); }); it('trailingSlash: ignore', async () => { diff --git a/packages/astro/test/astro-markdown.test.js b/packages/astro/test/astro-markdown.test.js index d0a49c873da4..e690c2c484e3 100644 --- a/packages/astro/test/astro-markdown.test.js +++ b/packages/astro/test/astro-markdown.test.js @@ -123,7 +123,7 @@ describe('Astro Markdown', () => { true, '"file" prop does not end with correct path or is undefined', ); - assert.equal(frontmatterUrl, '/with-layout'); + assert.equal(frontmatterUrl, '/with-layout/'); assert.equal(file, frontmatterFile); assert.equal(url, frontmatterUrl); }); diff --git a/packages/astro/test/astro-pagination-root-spread.test.js b/packages/astro/test/astro-pagination-root-spread.test.js index 3aad4d6419c1..2513cc3b16aa 100644 --- a/packages/astro/test/astro-pagination-root-spread.test.js +++ b/packages/astro/test/astro-pagination-root-spread.test.js @@ -17,8 +17,8 @@ describe('Pagination root', () => { it('correct prev url in root spread', async () => { const prevMap = { - '/4/': '/blog/3', - '/3/': '/blog/2', + '/4/': '/blog/3/', + '/3/': '/blog/2/', '/2/': '/blog/', '/': undefined, }; diff --git a/packages/astro/test/astro-pagination.test.js b/packages/astro/test/astro-pagination.test.js index f89fcb4f62a2..a755c9ae8177 100644 --- a/packages/astro/test/astro-pagination.test.js +++ b/packages/astro/test/astro-pagination.test.js @@ -58,10 +58,10 @@ describe('Pagination', () => { } if (color === 'blue' && p === '1') { assert.equal(prevHref, undefined); - assert.equal(nextHref, '/blog/posts/blue/2'); + assert.equal(nextHref, '/blog/posts/blue/2/'); } if (color === 'blue' && p === '2') { - assert.equal(prevHref, '/blog/posts/blue/1'); + assert.equal(prevHref, '/blog/posts/blue/1/'); assert.equal(nextHref, undefined); } }), diff --git a/packages/astro/test/content-collections-render.test.js b/packages/astro/test/content-collections-render.test.js index 5bb6fc529285..8011e45e948a 100644 --- a/packages/astro/test/content-collections-render.test.js +++ b/packages/astro/test/content-collections-render.test.js @@ -148,7 +148,7 @@ describe('Content Collections - render()', () => { it('Applies MDX components export', async () => { const app = await fixture.loadTestAdapterApp(); - const request = new Request('http://example.com/launch-week-components-export'); + const request = new Request('http://example.com/launch-week-components-export/'); const response = await app.render(request); const html = await response.text(); const $ = cheerio.load(html); @@ -161,7 +161,7 @@ describe('Content Collections - render()', () => { it('getCollection should return new instances of the array to be mutated safely', async () => { const app = await fixture.loadTestAdapterApp(); - let request = new Request('http://example.com/sort-blog-collection'); + let request = new Request('http://example.com/sort-blog-collection/'); let response = await app.render(request); let html = await response.text(); let $ = cheerio.load(html); diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js index 30acdc7bb10d..1445738c5edc 100644 --- a/packages/astro/test/core-image.test.js +++ b/packages/astro/test/core-image.test.js @@ -58,7 +58,7 @@ describe('astro:image', () => { it('Adds the tag', () => { let $img = $('#local img'); assert.equal($img.length, 1); - assert.equal($img.attr('src').startsWith('/_image'), true); + assert.equal($img.attr('src').startsWith('/_image/'), true); }); it('includes loading and decoding attributes', () => { @@ -99,6 +99,7 @@ describe('astro:image', () => { it('middleware loads the file', async () => { let $img = $('#local img'); let src = $img.attr('src'); + console.log('aaaaa', src) let res = await fixture.fetch(src); assert.equal(res.status, 200); }); @@ -143,7 +144,7 @@ describe('astro:image', () => { $img.toArray().every((img) => { return ( img.attribs['src'].startsWith('/@fs/') || - img.attribs['src'].startsWith('/_image?href=%2F%40fs%2F') + img.attribs['src'].startsWith('/_image/?href=%2F%40fs%2F') ); }), true, @@ -215,7 +216,7 @@ describe('astro:image', () => { const srcset = parseSrcset($source.attr('srcset')); assert.equal( - srcset.every((src) => src.url.startsWith('/_image')), + srcset.every((src) => src.url.startsWith('/_image/')), true, ); assert.deepEqual( @@ -237,7 +238,7 @@ describe('astro:image', () => { const srcset2 = parseSrcset($source.attr('srcset')); assert.equal( - srcset2.every((src) => src.url.startsWith('/_image')), + srcset2.every((src) => src.url.startsWith('/_image/')), true, ); assert.deepEqual( @@ -380,7 +381,7 @@ describe('astro:image', () => { let $img = $('#remote img'); let src = $img.attr('src'); - assert.ok(src.startsWith('/_image?')); + assert.ok(src.startsWith('/_image/?')); const imageRequest = await fixture.fetch(src); assert.equal(imageRequest.status, 200); }); @@ -463,7 +464,7 @@ describe('astro:image', () => { // Verbose test for the full URL to make sure the image went through the full pipeline assert.equal( - $img.attr('src').startsWith('/_image') && $img.attr('src').endsWith('f=webp'), + $img.attr('src').startsWith('/_image/') && $img.attr('src').endsWith('f=webp'), true, ); }); @@ -480,7 +481,7 @@ describe('astro:image', () => { $ = cheerio.load(html); let $img = $('img'); - assert.equal($img.attr('src').startsWith('/_image'), true); + assert.equal($img.attr('src').startsWith('/_image/'), true); }); it('Supports special characters in file name', async () => { @@ -491,7 +492,7 @@ describe('astro:image', () => { let $img = $('img'); assert.equal($img.length, 3); $img.each((_, el) => { - assert.equal(el.attribs.src?.startsWith('/_image'), true); + assert.equal(el.attribs.src?.startsWith('/_image/'), true); }); }); @@ -520,7 +521,7 @@ describe('astro:image', () => { it('Adds the tag', () => { let $img = $('img'); assert.equal($img.length, 1); - assert.equal($img.attr('src').startsWith('/_image'), true); + assert.equal($img.attr('src').startsWith('/_image/'), true); }); it('includes the provided alt', () => { @@ -572,14 +573,14 @@ describe('astro:image', () => { it('has proper attributes for optimized image through getImage', () => { let $img = $('#optimized-image-get-image img'); - assert.equal($img.attr('src').startsWith('/_image'), true); + assert.equal($img.attr('src').startsWith('/_image/'), true); assert.equal($img.attr('width'), '207'); assert.equal($img.attr('height'), '243'); }); it('has proper attributes for optimized image through Image component', () => { let $img = $('#optimized-image-component img'); - assert.equal($img.attr('src').startsWith('/_image'), true); + assert.equal($img.attr('src').startsWith('/_image/'), true); assert.equal($img.attr('width'), '207'); assert.equal($img.attr('height'), '243'); assert.equal($img.attr('alt'), 'A penguin!'); @@ -1109,11 +1110,11 @@ describe('astro:image', () => { await devServer.stop(); }); - it('serves the image at /_image', async () => { + it('serves the image at /_image/', async () => { const params = new URLSearchParams(); params.set('href', '/src/assets/penguin1.jpg?origWidth=207&origHeight=243&origFormat=jpg'); params.set('f', 'webp'); - const response = await fixture.fetch('/some-base/_image?' + String(params)); + const response = await fixture.fetch('/some-base/_image/?' + String(params)); assert.equal(response.status, 200); assert.equal(response.headers.get('content-type'), 'image/webp'); }); @@ -1229,7 +1230,7 @@ describe('astro:image', () => { const app = await fixture.loadTestAdapterApp(); for (const path of badPaths) { - let request = new Request('http://example.com/_image?href=' + path); + let request = new Request('http://example.com/_image/?href=' + path); let response = await app.render(request); const body = await response.text(); diff --git a/packages/astro/test/custom-404-injected.test.js b/packages/astro/test/custom-404-injected.test.js index 2dae399a269d..328693690417 100644 --- a/packages/astro/test/custom-404-injected.test.js +++ b/packages/astro/test/custom-404-injected.test.js @@ -40,7 +40,7 @@ describe('Custom 404 with injectRoute', () => { $ = cheerio.load(html); assert.equal($('h1').text(), 'Page not found'); - assert.equal($('p').text(), '/a'); + assert.equal($('p').text(), '/a/'); }); }); }); diff --git a/packages/astro/test/custom-404-static.test.js b/packages/astro/test/custom-404-static.test.js index ba351ae11c7d..fabdd9a2ebb9 100644 --- a/packages/astro/test/custom-404-static.test.js +++ b/packages/astro/test/custom-404-static.test.js @@ -40,7 +40,7 @@ describe('Custom 404 with Static', () => { $ = cheerio.load(html); assert.equal($('h1').text(), 'Page not found'); - assert.equal($('p').text(), '/a'); + assert.equal($('p').text(), '/a/'); }); }); diff --git a/packages/astro/test/experimental-content-collections.test.js b/packages/astro/test/experimental-content-collections.test.js index 32935215c30e..8f5a0ea4f2c5 100644 --- a/packages/astro/test/experimental-content-collections.test.js +++ b/packages/astro/test/experimental-content-collections.test.js @@ -353,7 +353,7 @@ describe('Experimental Content Collections cache', () => { it('Renders content', async () => { for (const slug in blogSlugToContents) { - const request = new Request('http://example.com/posts/' + slug); + const request = new Request('http://example.com/posts/' + slug + '/'); const response = await app.render(request); const body = await response.text(); const $ = cheerio.load(body); diff --git a/packages/astro/test/fixtures/astro-cookies/src/pages/from-endpoint.ts b/packages/astro/test/fixtures/astro-cookies/src/pages/from-endpoint.ts index 08c586dcf5b7..2f4c75ed50fd 100644 --- a/packages/astro/test/fixtures/astro-cookies/src/pages/from-endpoint.ts +++ b/packages/astro/test/fixtures/astro-cookies/src/pages/from-endpoint.ts @@ -1,3 +1,3 @@ export async function GET(context) { - return context.rewrite('/to-endpoint'); + return context.rewrite('/to-endpoint/'); } diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js index cc9bdc569f54..fab8c48c14c2 100644 --- a/packages/astro/test/i18n-routing.test.js +++ b/packages/astro/test/i18n-routing.test.js @@ -1171,7 +1171,7 @@ describe('[SSR] i18n routing', () => { }); it('renders the page', async () => { - let request = new Request('http://example.com/endurance'); + let request = new Request('http://example.com/endurance/'); let response = await app.render(request); assert.equal(response.status, 200); assert.equal((await response.text()).includes('Endurance'), true); @@ -1193,7 +1193,7 @@ describe('[SSR] i18n routing', () => { }); it('should redirect to the index of the default locale', async () => { - let request = new Request('http://example.com/new-site'); + let request = new Request('http://example.com/new-site/'); let response = await app.render(request); assert.equal(response.status, 302); assert.equal(response.headers.get('location'), '/new-site/en/'); diff --git a/packages/astro/test/units/routing/trailing-slash.test.js b/packages/astro/test/units/routing/trailing-slash.test.js index a9e8fe9451dd..44c0d667f34e 100644 --- a/packages/astro/test/units/routing/trailing-slash.test.js +++ b/packages/astro/test/units/routing/trailing-slash.test.js @@ -48,6 +48,7 @@ describe('trailingSlash', () => { assert.equal(json, '{"success":true}'); }); + // TODO: 301 redirect make it work it('should NOT match the API route when request lacks a trailing slash', async () => { const { req, res, text } = createRequestAndResponse({ method: 'GET',