Skip to content

Commit

Permalink
fix: add Content-Type header for POST requests made by enhance (#…
Browse files Browse the repository at this point in the history
…12198)

* test: Add test cases for `enctype` / `formenctype` attributes for `/actions/enhance/` test page

* fix: respect `enctype`/`formenctype` HTML form attributes from form + set `application/x-www-form-urlencoded` as a default `Content-Type` for `POST` requests made by `enhance`

* chore: Add a new changelog entry

* fix: Improve RegEx

Co-authored-by: Tee Ming <chewteeming01@gmail.com>

* refactor: Use `Headers` class instead of an regular object

* Update packages/kit/src/runtime/app/forms.js

* fix: Remove unneeded console log from test code

Co-authored-by: Tee Ming <chewteeming01@gmail.com>

* fix: Apply suggestions from code review for test files

Co-authored-by: Tee Ming <chewteeming01@gmail.com>

* chore: Linting

* test: Check default `ContentType` value for request made by `use:enhance`

* test: Remove unneeded test

* refactor: Better logic for handling manual `Content-Type` set by `enctype` / `formenctype`

* chore: Formatting

* chore: Update changeset

* Delete .changeset/moody-apricots-kiss.md

* separate changesets

* cleanup

* move closer to other button tests

---------

Co-authored-by: Aleksander Grygier <aleksander.grygier@resider.pl>
Co-authored-by: Tee Ming <chewteeming01@gmail.com>
  • Loading branch information
3 people authored Jun 24, 2024
1 parent 338ee63 commit 8429847
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/hot-papayas-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: respect HTML attributes `enctype` and `formenctype` for forms with `use:enhance`
5 changes: 5 additions & 0 deletions .changeset/strange-planets-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/kit": patch
---

fix: set default `Content-Type` header to `application/x-www-form-urlencoded` for `POST` form submissions with `use:enhance` to align with native form behaviour
33 changes: 27 additions & 6 deletions packages/kit/src/runtime/app/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,13 @@ export function enhance(form_element, submit = () => {}) {
: clone(form_element).action
);

const enctype = event.submitter?.hasAttribute('formenctype')
? /** @type {HTMLButtonElement | HTMLInputElement} */ (event.submitter).formEnctype
: clone(form_element).enctype;

const form_data = new FormData(form_element);

if (DEV && clone(form_element).enctype !== 'multipart/form-data') {
if (DEV && enctype !== 'multipart/form-data') {
for (const value of form_data.values()) {
if (value instanceof File) {
throw new Error(
Expand Down Expand Up @@ -161,14 +165,31 @@ export function enhance(form_element, submit = () => {}) {
let result;

try {
const headers = new Headers({
accept: 'application/json',
'x-sveltekit-action': 'true'
});

// do not explicitly set the `Content-Type` header when sending `FormData`
// or else it will interfere with the browser's header setting
// see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects#sect4
if (enctype !== 'multipart/form-data') {
headers.set(
'Content-Type',
/^(:?application\/x-www-form-urlencoded|text\/plain)$/.test(enctype)
? enctype
: 'application/x-www-form-urlencoded'
);
}

// @ts-expect-error `URLSearchParams(form_data)` is kosher, but typescript doesn't know that
const body = enctype === 'multipart/form-data' ? form_data : new URLSearchParams(form_data);

const response = await fetch(action, {
method: 'POST',
headers: {
accept: 'application/json',
'x-sveltekit-action': 'true'
},
headers,
cache: 'no-store',
body: form_data,
body,
signal: controller.signal
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ export const actions = {
path: '/actions/enhance'
});

return {};
},
send_file: async ({ request }) => {
const data = await request.formData();
const file = data.get('file');

if (file instanceof File) {
return {
result: 'file name:' + file.name
};
}
return {};
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,9 @@
<button formmethod="dialog">Cancel</button>
</form>
</dialog>

<form action="?/send_file" method="post" use:enhance>
<input type="file" name="file" class="form-file-input" />

<button class="form-file-submit" formenctype="multipart/form-data" type="submit">Submit</button>
</form>
51 changes: 51 additions & 0 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,57 @@ test.describe('Actions', () => {
);
});

test('use:enhance button with formenctype', async ({ page }) => {
await page.goto('/actions/enhance');

expect(await page.textContent('pre.formdata1')).toBe(JSON.stringify(null));
expect(await page.textContent('pre.formdata2')).toBe(JSON.stringify(null));

const fileInput = page.locator('input[type="file"].form-file-input');

await fileInput.setInputFiles({
name: 'test-file.txt',
mimeType: 'text/plain',
buffer: Buffer.from('this is test')
});

await page.locator('button.form-file-submit').click();

await expect(page.locator('pre.formdata1')).toHaveText(
JSON.stringify({ result: 'file name:test-file.txt' })
);
await expect(page.locator('pre.formdata2')).toHaveText(
JSON.stringify({ result: 'file name:test-file.txt' })
);
});

test('use:enhance has `application/x-www-form-urlencoded` as default value for `ContentType` request header', async ({
page,
javaScriptEnabled
}) => {
test.skip(!javaScriptEnabled, 'skip when JavaScript is disabled');

await page.goto('/actions/enhance');

expect(await page.textContent('pre.formdata1')).toBe(JSON.stringify(null));
expect(await page.textContent('pre.formdata2')).toBe(JSON.stringify(null));

await page.locator('input[name="username"]').fill('foo');

const [request] = await Promise.all([
page.waitForRequest('/actions/enhance?/login'),
page.locator('button.form1').click()
]);

const requestHeaders = await request.allHeaders();

expect(requestHeaders['content-type']).toBe('application/x-www-form-urlencoded');

await expect(page.locator('pre.formdata1')).toHaveText(JSON.stringify({ result: 'foo' }));
await expect(page.locator('pre.formdata2')).toHaveText(JSON.stringify({ result: 'foo' }));
await expect(page.locator('input[name="username"]')).toHaveValue('');
});

test('use:enhance does not clear form on second submit', async ({ page }) => {
await page.goto('/actions/enhance');

Expand Down

0 comments on commit 8429847

Please sign in to comment.