From 5062d27a186c5020522614b9d6f3da218f7afd96 Mon Sep 17 00:00:00 2001 From: Ken Powers Date: Wed, 20 Dec 2023 07:37:48 -0500 Subject: [PATCH] Respect forms with enctype set for view transitions (#9466) * Respect forms with enctype set for view transitions * Add changeset * Revert "Respect forms with enctype set for view transitions" This reverts commit 6d3e04a9598a2de8ccc153911fd6ab629abcaf7a. * Review feedback * Handle submitter case * Move comment * Update .changeset/rude-geckos-rush.md * Add tests --------- Co-authored-by: Nate Moore --- .changeset/rude-geckos-rush.md | 5 ++ .../view-transitions/src/pages/form-one.astro | 10 ++- packages/astro/e2e/view-transitions.test.js | 77 +++++++++++++++++++ packages/astro/src/transitions/router.ts | 20 ++++- 4 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 .changeset/rude-geckos-rush.md diff --git a/.changeset/rude-geckos-rush.md b/.changeset/rude-geckos-rush.md new file mode 100644 index 000000000000..53f058151490 --- /dev/null +++ b/.changeset/rude-geckos-rush.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Updates view transitions `form` handling with logic for the [`enctype`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype) attribute diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro index daa03b723d51..88a36251a72a 100644 --- a/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro @@ -1,13 +1,15 @@ --- import Layout from '../components/Layout.astro'; const method = Astro.url.searchParams.get('method') ?? 'POST'; +const enctype = Astro.url.searchParams.get('enctype'); const postShowThrow = Astro.url.searchParams.has('throw') ?? false; --- +

Contact Form

-
- - {postShowThrow ? : ''} - + + + {postShowThrow ? : ''} +
diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js index b755fd0d37f1..125caf00cd3e 100644 --- a/packages/astro/e2e/view-transitions.test.js +++ b/packages/astro/e2e/view-transitions.test.js @@ -976,6 +976,83 @@ test.describe('View Transitions', () => { ).toEqual(1); }); + test('form POST defaults to multipart/form-data (Astro 4.x compatibility)', async ({ + page, + astro, + }) => { + const loads = []; + + page.addListener('load', async (p) => { + loads.push(p); + }); + + const postedEncodings = []; + + await page.route('**/contact', async (route) => { + const request = route.request(); + + if (request.method() === 'POST') { + postedEncodings.push(request.headers()['content-type'].split(';')[0]); + } + + await route.continue(); + }); + + await page.goto(astro.resolveUrl('/form-one')); + + // Submit the form + await page.click('#submit'); + + expect( + loads.length, + 'There should be only 1 page load. No additional loads for the form submission' + ).toEqual(1); + + expect( + postedEncodings, + 'There should be 1 POST, with encoding set to `multipart/form-data`' + ).toEqual(['multipart/form-data']); + }); + + test('form POST respects enctype attribute', async ({ page, astro }) => { + const loads = []; + + page.addListener('load', async (p) => { + loads.push(p); + }); + + const postedEncodings = []; + + await page.route('**/contact', async (route) => { + const request = route.request(); + + if (request.method() === 'POST') { + postedEncodings.push(request.headers()['content-type'].split(';')[0]); + } + + await route.continue(); + }); + + await page.goto( + astro.resolveUrl( + `/form-one?${new URLSearchParams({ enctype: 'application/x-www-form-urlencoded' })}` + ) + ); + + // Submit the form + await page.click('#submit'); + + expect( + loads.length, + 'There should be only 1 page load. No additional loads for the form submission' + ).toEqual(1); + + expect( + postedEncodings, + 'There should be 1 POST, with encoding set to `multipart/form-data`' + ).toEqual(['application/x-www-form-urlencoded']); + }); + test('Route announcer is invisible on page transition', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/no-directive-one')); diff --git a/packages/astro/src/transitions/router.ts b/packages/astro/src/transitions/router.ts index 6588fd71f960..98cdf2066f71 100644 --- a/packages/astro/src/transitions/router.ts +++ b/packages/astro/src/transitions/router.ts @@ -463,7 +463,25 @@ async function transition( const init: RequestInit = {}; if (preparationEvent.formData) { init.method = 'POST'; - init.body = preparationEvent.formData; + const form = + preparationEvent.sourceElement instanceof HTMLFormElement + ? preparationEvent.sourceElement + : preparationEvent.sourceElement instanceof HTMLElement && + 'form' in preparationEvent.sourceElement + ? (preparationEvent.sourceElement.form as HTMLFormElement) + : preparationEvent.sourceElement?.closest('form'); + // Form elements without enctype explicitly set default to application/x-www-form-urlencoded. + // In order to maintain compatibility with Astro 4.x, we need to check the value of enctype + // on the attributes property rather than accessing .enctype directly. Astro 5.x may + // introduce defaulting to application/x-www-form-urlencoded as a breaking change, and then + // we can access .enctype directly. + // + // Note: getNamedItem can return null in real life, even if TypeScript doesn't think so, hence + // the ?. + init.body = + form?.attributes.getNamedItem('enctype')?.value === 'application/x-www-form-urlencoded' + ? new URLSearchParams(preparationEvent.formData as any) + : preparationEvent.formData; } const response = await fetchHTML(href, init); // If there is a problem fetching the new page, just do an MPA navigation to it.