diff options
-rw-r--r-- | .changeset/rude-geckos-rush.md | 5 | ||||
-rw-r--r-- | packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro | 10 | ||||
-rw-r--r-- | packages/astro/e2e/view-transitions.test.js | 77 | ||||
-rw-r--r-- | packages/astro/src/transitions/router.ts | 20 |
4 files changed, 107 insertions, 5 deletions
diff --git a/.changeset/rude-geckos-rush.md b/.changeset/rude-geckos-rush.md new file mode 100644 index 000000000..53f058151 --- /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 daa03b723..88a36251a 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; --- + <Layout> <h2>Contact Form</h2> - <form action="/contact" method={method}> - <input type="hidden" name="name" value="Testing"> - {postShowThrow ? <input type="hidden" name="throw" value="true"> : ''} - <input type="submit" value="Submit" id="submit"> + <form action="/contact" method={method} {...enctype ? { enctype } : {}}> + <input type="hidden" name="name" value="Testing" /> + {postShowThrow ? <input type="hidden" name="throw" value="true" /> : ''} + <input type="submit" value="Submit" id="submit" /> </form> </Layout> diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js index b755fd0d3..125caf00c 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 6588fd71f..98cdf2066 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. |