set_reverse_message
} from './form.remote.js';
- const message = get_message();
+ const { params } = $props();
- const scoped = set_message.for('scoped');
- const enhanced = set_message.for('enhanced');
+ const message = get_message(params.test_name);
+
+ const scoped = set_message.for(`scoped:${params.test_name}`);
+ const enhanced = set_message.for(`enhanced:${params.test_name}`);
</script>
<p>message.current: {message.current}</p>
{/if}
<input {...set_message.fields.message.as('text')} />
+ <input {...set_message.fields.test_name.as('hidden', params.test_name)} />
+ <!--
+ NOTE: there really probably should be a `set_reverse_message' test_name hidden field here, but it collides with the one above.
+ This kind of lines up with our discussions from earlier where we were talking about needing to include the RF hash in the field name.
+ If we do that and this test starts failing, all we'll need to do is add the hidden field back in.
+ -->
<button>set message</button>
<button {...set_reverse_message.buttonProps}>set reverse message</button>
</form>
{/if}
<input {...scoped.fields.message.as('text')} />
+ <input {...scoped.fields.test_name.as('hidden', params.test_name)} />
<button>set scoped message</button>
</form>
<form
data-enhanced
{...enhanced.enhance(async ({ data, submit }) => {
- await submit().updates(get_message().withOverride(() => data.message + ' (override)'));
+ await submit().updates(
+ get_message(params.test_name).withOverride(() => data.message + ' (override)')
+ );
})}
>
{#if enhanced.fields.message.issues()}
{/if}
<input {...enhanced.fields.message.as('text')} />
+ <input {...enhanced.fields.test_name.as('hidden', params.test_name)} />
<button><span>set enhanced message</span></button>
</form>
<form {...resolve_deferreds}>
<button>resolve deferreds</button>
+ <input {...resolve_deferreds.fields.test_name.as('hidden', params.test_name)} />
</form>
--- /dev/null
+import { form, getRequestEvent, query } from '$app/server';
+import { error, redirect } from '@sveltejs/kit';
+import * as v from 'valibot';
+
+const instances = new Map<
+ string,
+ { message: string; deferreds: Array<PromiseWithResolvers<void>> }
+>();
+
+export const get_message = query(v.string(), (test_name) => {
+ return instances.get(test_name)?.message || 'initial';
+});
+
+export const set_message = form(
+ v.object({
+ test_name: v.string(),
+ id: v.optional(v.string()),
+ message: v.picklist(
+ ['hello', 'goodbye', 'unexpected error', 'expected error', 'redirect'],
+ 'message is invalid'
+ ),
+ uppercase: v.optional(v.string())
+ }),
+ async (data) => {
+ if (data.message === 'unexpected error') {
+ throw new Error('oops');
+ }
+
+ if (data.message === 'expected error') {
+ error(500, 'oops');
+ }
+
+ if (data.message === 'redirect') {
+ redirect(303, '/remote');
+ }
+
+ const instance = instances.get(data.test_name) ?? { message: 'initial', deferreds: [] };
+ instances.set(data.test_name, instance);
+
+ instance.message = data.uppercase === 'true' ? data.message.toUpperCase() : data.message;
+
+ if (getRequestEvent().isRemoteRequest) {
+ const deferred = Promise.withResolvers<void>();
+ instance.deferreds.push(deferred);
+ await deferred.promise;
+ }
+
+ return instance.message + (data.id ? ` (from: ${data.id})` : '');
+ }
+);
+
+export const set_reverse_message = form(
+ v.object({ test_name: v.string(), message: v.string() }),
+ (data) => {
+ const instance = instances.get(data.test_name) ?? { message: 'initial', deferreds: [] };
+ instances.set(data.test_name, instance);
+ instance.message = data.message.split('').reverse().join('');
+ return instance.message;
+ }
+);
+
+export const resolve_deferreds = form(v.object({ test_name: v.string() }), async (data) => {
+ const instance = instances.get(data.test_name);
+ if (!instance) return;
+
+ const { deferreds } = instance;
+ for (const deferred of deferreds) {
+ deferred.resolve();
+ }
+ deferreds.length = 0;
+});
+++ /dev/null
-import { form, getRequestEvent, query } from '$app/server';
-import { error, redirect } from '@sveltejs/kit';
-import * as v from 'valibot';
-
-let message = 'initial';
-const deferreds = [];
-
-export const get_message = query(() => {
- return message;
-});
-
-export const set_message = form(
- v.object({
- id: v.optional(v.string()),
- message: v.picklist(
- ['hello', 'goodbye', 'unexpected error', 'expected error', 'redirect'],
- 'message is invalid'
- ),
- uppercase: v.optional(v.string())
- }),
- async (data) => {
- if (data.message === 'unexpected error') {
- throw new Error('oops');
- }
-
- if (data.message === 'expected error') {
- error(500, 'oops');
- }
-
- if (data.message === 'redirect') {
- redirect(303, '/remote');
- }
-
- message = data.uppercase === 'true' ? data.message.toUpperCase() : data.message;
-
- if (getRequestEvent().isRemoteRequest) {
- const deferred = Promise.withResolvers();
- deferreds.push(deferred);
- await deferred.promise;
- }
-
- return message + (data.id ? ` (from: ${data.id})` : '');
- }
-);
-
-export const set_reverse_message = form(v.object({ message: v.string() }), (data) => {
- message = data.message.split('').reverse().join('');
- return message;
-});
-
-export const resolve_deferreds = form(async () => {
- for (const deferred of deferreds) {
- deferred.resolve();
- }
- deferreds.length = 0;
-});
<script lang="ts">
- import { set_message } from '../form.remote.js';
+ import { set_message } from '../[test_name]/form.remote.js';
import * as v from 'valibot';
const schema = v.object({
+ test_name: v.string(),
message: v.picklist(
['hello', 'goodbye', 'unexpected error', 'expected error', 'redirect'],
'message is invalid'
<label>
<span>Message</span>
<input {...set_message.fields.message.as('text')} />
+ <input {...set_message.fields.test_name.as('hidden', 'imperative')} />
</label>
<p id="issue">
<li><a href="#p2">second paragraph</a></li>
</ol>
-<p id="p1">paragraph 1</p>
-<p id="p2">paragraph 2</p>
+<p tabindex="-1" id="p1">paragraph 1</p>
+<p tabindex="-1" id="p2">paragraph 2</p>
<li><button>next focus element</button></li>
test('focus works if page load has hash', async ({ page, browserName }) => {
await page.goto('/routing/hashes/target#p2');
+ await page.waitForSelector('#p2:focus');
await page.keyboard.press(browserName === 'webkit' ? 'Alt+Tab' : 'Tab');
-
await page.waitForSelector('button:focus');
});
});
test('form works', async ({ page, javaScriptEnabled }) => {
- await page.goto('/remote/form');
+ await page.goto('/remote/form/basic');
if (javaScriptEnabled) {
// TODO remove the `if` — once async SSR lands these assertions should always succeed
}
await expect(page.getByText('set_message.result')).toHaveText('set_message.result: hello');
- await expect(page.locator('[data-unscoped] input')).toHaveValue('');
+ await expect(page.locator('[data-unscoped] input[name="message"]')).toHaveValue('');
});
test('form submitters work', async ({ page }) => {
});
test('form updates inputs live', async ({ page, javaScriptEnabled }) => {
- await page.goto('/remote/form');
+ await page.goto('/remote/form/live-update');
await page.fill('input', 'hello');
});
test('form reports validation issues', async ({ page }) => {
- await page.goto('/remote/form');
+ await page.goto('/remote/form/validation-issues');
await page.fill('input', 'invalid');
await page.getByText('set message').click();
});
test('form handles unexpected error', async ({ page }) => {
- await page.goto('/remote/form');
+ await page.goto('/remote/form/unexpected-error');
await page.fill('input', 'unexpected error');
await page.getByText('set message').click();
});
test('form handles expected error', async ({ page }) => {
- await page.goto('/remote/form');
+ await page.goto('/remote/form/expected-error');
await page.fill('input', 'expected error');
await page.getByText('set message').click();
});
test('form redirects', async ({ page }) => {
- await page.goto('/remote/form');
+ await page.goto('/remote/form/redirect');
await page.fill('input', 'redirect');
await page.getByText('set message').click();
});
test('form.buttonProps works', async ({ page, javaScriptEnabled }) => {
- await page.goto('/remote/form');
+ await page.goto('/remote/form/button-props');
await page.fill('[data-unscoped] input', 'backwards');
await page.getByText('set reverse message').click();
});
test('form scoping with for(...) works', async ({ page, javaScriptEnabled }) => {
- await page.goto('/remote/form');
+ await page.goto('/remote/form/form-scoped');
await page.fill('[data-scoped] input', 'hello');
await page.getByText('set scoped message').click();
await expect(page.getByText('await get_message():')).toHaveText('await get_message(): hello');
}
- await expect(page.getByText('scoped.result')).toHaveText('scoped.result: hello (from: scoped)');
- await expect(page.locator('[data-scoped] input')).toHaveValue('');
+ await expect(page.getByText('scoped.result')).toHaveText(
+ 'scoped.result: hello (from: scoped:form-scoped)'
+ );
+ await expect(page.locator('[data-scoped] input[name="message"]')).toHaveValue('');
});
test('form enhance(...) works', async ({ page, javaScriptEnabled }) => {
- await page.goto('/remote/form');
+ await page.goto('/remote/form/enhanced');
await page.fill('[data-enhanced] input', 'hello');
await expect(page.getByText('await get_message():')).toHaveText('await get_message(): hello');
// enhanced submission should not clear the input; the developer must do that at the appropriate time
- await expect(page.locator('[data-enhanced] input')).toHaveValue('hello');
+ await expect(page.locator('[data-enhanced] input[name="message"]')).toHaveValue('hello');
} else {
- await expect(page.locator('[data-enhanced] input')).toHaveValue('');
+ await expect(page.locator('[data-enhanced] input[name="message"]')).toHaveValue('');
}
await expect(page.getByText('enhanced.result')).toHaveText(
- 'enhanced.result: hello (from: enhanced)'
+ 'enhanced.result: hello (from: enhanced:enhanced)'
);
});