diff --git a/packages/svelte/src/reactivity/url-search-params.js b/packages/svelte/src/reactivity/url-search-params.js index 0aaccb111a..03387d20c1 100644 --- a/packages/svelte/src/reactivity/url-search-params.js +++ b/packages/svelte/src/reactivity/url-search-params.js @@ -3,9 +3,14 @@ import { get } from '../internal/client/runtime.js'; import { increment } from './utils.js'; export const REPLACE = Symbol(); +export const onChange = Symbol(); export class SvelteURLSearchParams extends URLSearchParams { #version = source(0); + /** + * @type {((command: "set" | "append" | "delete" | "sort", ...args: any[])=>void ) | null} + */ + [onChange] = null; /** * @param {URLSearchParams} params @@ -28,8 +33,10 @@ export class SvelteURLSearchParams extends URLSearchParams { * @returns {void} */ append(name, value) { + var res = super.append(name, value); increment(this.#version); - return super.append(name, value); + this[onChange]?.('append', name, value); + return res; } /** @@ -42,6 +49,7 @@ export class SvelteURLSearchParams extends URLSearchParams { var res = super.delete(name, value); if (has_value) { increment(this.#version); + this[onChange]?.('delete', name, value); } return res; } @@ -91,12 +99,14 @@ export class SvelteURLSearchParams extends URLSearchParams { // if you set `foo` to 1, then foo=3 gets deleted whilst `has("foo", "1")` returns true if (value_before_change !== super.getAll(name).join('')) { increment(this.#version); + this[onChange]?.('set', name, value); } } sort() { var res = super.sort(); increment(this.#version); + this[onChange]?.('sort'); return res; } diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index bf575b9e2c..822481c428 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -1,6 +1,6 @@ import { source, set } from '../internal/client/reactivity/sources.js'; import { get } from '../internal/client/runtime.js'; -import { REPLACE, SvelteURLSearchParams } from './url-search-params.js'; +import { onChange, REPLACE, SvelteURLSearchParams } from './url-search-params.js'; export class SvelteURL extends URL { #protocol = source(super.protocol); @@ -20,6 +20,11 @@ export class SvelteURL extends URL { url = new URL(url, base); super(url); this.#searchParams[REPLACE](url.searchParams); + this.#searchParams[onChange] = (command, ...args) => { + // by doing this, we sync SvelteSearchParams with internal implementation of URL.searchParams + // @ts-ignore + super.searchParams[command](...args); + }; } get hash() { diff --git a/packages/svelte/src/reactivity/url.test.ts b/packages/svelte/src/reactivity/url.test.ts index 6956ae0198..d93fb90afc 100644 --- a/packages/svelte/src/reactivity/url.test.ts +++ b/packages/svelte/src/reactivity/url.test.ts @@ -4,7 +4,7 @@ import { SvelteURL } from './url.js'; import { assert, test } from 'vitest'; test('url.hash', () => { - const url = new SvelteURL('http://google.com'); + const url = new SvelteURL('https://svelte.dev'); const log: any = []; const cleanup = effect_root(() => { @@ -18,7 +18,7 @@ test('url.hash', () => { }); flushSync(() => { - url.href = 'http://google.com/a/b/c#def'; + url.href = 'https://svelte.dev/a/b/c#def'; }); flushSync(() => { @@ -31,6 +31,44 @@ test('url.hash', () => { cleanup(); }); +test('url.href', () => { + const url = new SvelteURL('https://svelte.dev?foo=bar&t=123'); + const log: any = []; + + const cleanup = effect_root(() => { + render_effect(() => { + log.push(url.href); + }); + }); + + flushSync(() => { + url.search = '?q=kit&foo=baz'; + }); + + flushSync(() => { + // changes from searchParams should be synced to URL instance as well + url.searchParams.append('foo', 'qux'); + }); + + flushSync(() => { + url.searchParams.delete('foo'); + }); + + flushSync(() => { + url.searchParams.set('love', 'svelte5'); + }); + + assert.deepEqual(log, [ + 'https://svelte.dev/?foo=bar&t=123', + 'https://svelte.dev/?q=kit&foo=baz', + 'https://svelte.dev/?q=kit&foo=baz&foo=qux', + 'https://svelte.dev/?q=kit', + 'https://svelte.dev/?q=kit&love=svelte5' + ]); + + cleanup(); +}); + test('url.searchParams', () => { const url = new SvelteURL('https://svelte.dev?foo=bar&t=123'); const log: any = []; @@ -76,3 +114,7 @@ test('url.searchParams', () => { cleanup(); }); + +test('URL.instanceOf', () => { + assert.equal(new SvelteURL('https://svelte.dev') instanceof URL, true); +}); diff --git a/vitest.config.js b/vitest.config.js index 86c5c99d0e..caeda27e30 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -30,9 +30,9 @@ export default defineConfig({ dir: '.', reporters: ['dot'], include: [ - 'packages/svelte/**/*.test.ts' - // 'packages/svelte/tests/*/test.ts', - // 'packages/svelte/tests/runtime-browser/test-ssr.ts' + 'packages/svelte/**/*.test.ts', + 'packages/svelte/tests/*/test.ts', + 'packages/svelte/tests/runtime-browser/test-ssr.ts' ], exclude: [...configDefaults.exclude, '**/samples/**'], coverage: {