summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/rude-geckos-rush.md5
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/src/pages/form-one.astro10
-rw-r--r--packages/astro/e2e/view-transitions.test.js77
-rw-r--r--packages/astro/src/transitions/router.ts20
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.