Skip to content

Commit 5062d27

Browse files
knpwrsnatemoo-re
andauthoredDec 20, 2023
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 6d3e04a. * Review feedback * Handle submitter case * Move comment * Update .changeset/rude-geckos-rush.md * Add tests --------- Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
1 parent 1f3d72b commit 5062d27

File tree

4 files changed

+107
-5
lines changed

4 files changed

+107
-5
lines changed
 

‎.changeset/rude-geckos-rush.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Updates view transitions `form` handling with logic for the [`enctype`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype) attribute
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
---
22
import Layout from '../components/Layout.astro';
33
const method = Astro.url.searchParams.get('method') ?? 'POST';
4+
const enctype = Astro.url.searchParams.get('enctype');
45
const postShowThrow = Astro.url.searchParams.has('throw') ?? false;
56
---
7+
68
<Layout>
79
<h2>Contact Form</h2>
8-
<form action="/contact" method={method}>
9-
<input type="hidden" name="name" value="Testing">
10-
{postShowThrow ? <input type="hidden" name="throw" value="true"> : ''}
11-
<input type="submit" value="Submit" id="submit">
10+
<form action="/contact" method={method} {...enctype ? { enctype } : {}}>
11+
<input type="hidden" name="name" value="Testing" />
12+
{postShowThrow ? <input type="hidden" name="throw" value="true" /> : ''}
13+
<input type="submit" value="Submit" id="submit" />
1214
</form>
1315
</Layout>

‎packages/astro/e2e/view-transitions.test.js

+77
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,83 @@ test.describe('View Transitions', () => {
976976
).toEqual(1);
977977
});
978978

979+
test('form POST defaults to multipart/form-data (Astro 4.x compatibility)', async ({
980+
page,
981+
astro,
982+
}) => {
983+
const loads = [];
984+
985+
page.addListener('load', async (p) => {
986+
loads.push(p);
987+
});
988+
989+
const postedEncodings = [];
990+
991+
await page.route('**/contact', async (route) => {
992+
const request = route.request();
993+
994+
if (request.method() === 'POST') {
995+
postedEncodings.push(request.headers()['content-type'].split(';')[0]);
996+
}
997+
998+
await route.continue();
999+
});
1000+
1001+
await page.goto(astro.resolveUrl('/form-one'));
1002+
1003+
// Submit the form
1004+
await page.click('#submit');
1005+
1006+
expect(
1007+
loads.length,
1008+
'There should be only 1 page load. No additional loads for the form submission'
1009+
).toEqual(1);
1010+
1011+
expect(
1012+
postedEncodings,
1013+
'There should be 1 POST, with encoding set to `multipart/form-data`'
1014+
).toEqual(['multipart/form-data']);
1015+
});
1016+
1017+
test('form POST respects enctype attribute', async ({ page, astro }) => {
1018+
const loads = [];
1019+
1020+
page.addListener('load', async (p) => {
1021+
loads.push(p);
1022+
});
1023+
1024+
const postedEncodings = [];
1025+
1026+
await page.route('**/contact', async (route) => {
1027+
const request = route.request();
1028+
1029+
if (request.method() === 'POST') {
1030+
postedEncodings.push(request.headers()['content-type'].split(';')[0]);
1031+
}
1032+
1033+
await route.continue();
1034+
});
1035+
1036+
await page.goto(
1037+
astro.resolveUrl(
1038+
`/form-one?${new URLSearchParams({ enctype: 'application/x-www-form-urlencoded' })}`
1039+
)
1040+
);
1041+
1042+
// Submit the form
1043+
await page.click('#submit');
1044+
1045+
expect(
1046+
loads.length,
1047+
'There should be only 1 page load. No additional loads for the form submission'
1048+
).toEqual(1);
1049+
1050+
expect(
1051+
postedEncodings,
1052+
'There should be 1 POST, with encoding set to `multipart/form-data`'
1053+
).toEqual(['application/x-www-form-urlencoded']);
1054+
});
1055+
9791056
test('Route announcer is invisible on page transition', async ({ page, astro }) => {
9801057
await page.goto(astro.resolveUrl('/no-directive-one'));
9811058

‎packages/astro/src/transitions/router.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,25 @@ async function transition(
463463
const init: RequestInit = {};
464464
if (preparationEvent.formData) {
465465
init.method = 'POST';
466-
init.body = preparationEvent.formData;
466+
const form =
467+
preparationEvent.sourceElement instanceof HTMLFormElement
468+
? preparationEvent.sourceElement
469+
: preparationEvent.sourceElement instanceof HTMLElement &&
470+
'form' in preparationEvent.sourceElement
471+
? (preparationEvent.sourceElement.form as HTMLFormElement)
472+
: preparationEvent.sourceElement?.closest('form');
473+
// Form elements without enctype explicitly set default to application/x-www-form-urlencoded.
474+
// In order to maintain compatibility with Astro 4.x, we need to check the value of enctype
475+
// on the attributes property rather than accessing .enctype directly. Astro 5.x may
476+
// introduce defaulting to application/x-www-form-urlencoded as a breaking change, and then
477+
// we can access .enctype directly.
478+
//
479+
// Note: getNamedItem can return null in real life, even if TypeScript doesn't think so, hence
480+
// the ?.
481+
init.body =
482+
form?.attributes.getNamedItem('enctype')?.value === 'application/x-www-form-urlencoded'
483+
? new URLSearchParams(preparationEvent.formData as any)
484+
: preparationEvent.formData;
467485
}
468486
const response = await fetchHTML(href, init);
469487
// If there is a problem fetching the new page, just do an MPA navigation to it.

0 commit comments

Comments
 (0)
Please sign in to comment.