From 88ef883f6ac41abc0339456298bca3234382e50d Mon Sep 17 00:00:00 2001 From: Ramon Snir Date: Fri, 14 Oct 2022 18:22:11 +0000 Subject: [PATCH 1/4] [fix] allow $store to be used with changing values including nullish values --- src/runtime/internal/utils.ts | 3 +++ .../_config.js | 21 +++++++++++++++++++ .../main.svelte | 5 +++++ 3 files changed, 29 insertions(+) create mode 100644 test/runtime/samples/store-auto-subscribe-removed-store/_config.js create mode 100644 test/runtime/samples/store-auto-subscribe-removed-store/main.svelte diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 8868e38ee29f..c9e37eef3783 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -66,6 +66,9 @@ export function validate_store(store, name) { export function subscribe(store, ...callbacks) { if (store == null) { + for (const callback of callbacks) { + callback(store); + } return noop; } const unsub = store.subscribe(...callbacks); diff --git a/test/runtime/samples/store-auto-subscribe-removed-store/_config.js b/test/runtime/samples/store-auto-subscribe-removed-store/_config.js new file mode 100644 index 000000000000..12297a3b8a77 --- /dev/null +++ b/test/runtime/samples/store-auto-subscribe-removed-store/_config.js @@ -0,0 +1,21 @@ +import { writable } from '../../../../store'; + +export default { + html: ` +

undefined

+ `, + async test({ assert, component, target }) { + component.store = writable('foo'); + assert.htmlEqual(target.innerHTML, ` +

foo

+ `); + component.store = undefined; + assert.htmlEqual(target.innerHTML, ` +

undefined

+ `); + component.store = writable('bar'); + assert.htmlEqual(target.innerHTML, ` +

bar

+ `); + } +}; diff --git a/test/runtime/samples/store-auto-subscribe-removed-store/main.svelte b/test/runtime/samples/store-auto-subscribe-removed-store/main.svelte new file mode 100644 index 000000000000..9c1ed4a56094 --- /dev/null +++ b/test/runtime/samples/store-auto-subscribe-removed-store/main.svelte @@ -0,0 +1,5 @@ + + +

{$store}

From 1581da2e0800a5ac0984c1e979b6cf1ee38f1f0f Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 28 Apr 2023 09:55:04 +0200 Subject: [PATCH 2/4] consistently handle falsy store values for derived, add tests --- src/runtime/internal/utils.ts | 2 +- src/runtime/store/index.ts | 5 +++-- test/store/index.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 979b418596b2..9d48378603f8 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -69,7 +69,7 @@ export function validate_store(store, name) { export function subscribe(store, ...callbacks) { if (store == null) { for (const callback of callbacks) { - callback(store); + callback(undefined); } return noop; } diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 09e5b10bd2ba..13131d4cc0e4 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -165,8 +165,9 @@ export function derived( export function derived(stores: Stores, fn: Function, initial_value?: T): Readable { const single = !Array.isArray(stores); const stores_array: Array> = single - ? [stores as Readable] - : stores as Array>; + // Fall back to readable() for falsy stores in array, else derived store will be forever pending + ? [(stores || readable()) as Readable] + : (stores as Array>).map(store => store || readable()); const auto = fn.length < 2; diff --git a/test/store/index.js b/test/store/index.js index 29d233495c33..d9c806a48677 100644 --- a/test/store/index.js +++ b/test/store/index.js @@ -428,6 +428,39 @@ describe('store', () => { a.set(false); assert.equal(b_started, false); }); + + it('works with undefined stores #1', () => { + const a = derived(null, (n) => { + return n; + }); + const values = []; + const unsubscribe = a.subscribe((value) => values.push(value)); + unsubscribe(); + assert.deepEqual(values, [undefined]); + }); + + it('works with undefined stores #2', () => { + const a = writable(1); + const b = derived([a, null, undefined], ([n, un1, un2]) => { + assert.equal(un1, undefined); + assert.equal(un2, undefined); + return n * 2; + }); + + const values = []; + + const unsubscribe = b.subscribe(value => { + values.push(value); + }); + + a.set(2); + assert.deepEqual(values, [2, 4]); + + unsubscribe(); + + a.set(3); + assert.deepEqual(values, [2, 4]); + }); }); describe('get', () => { From 85d45cb221f497a9d85551df13aaf421eed73eba Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 4 May 2023 15:08:47 +0200 Subject: [PATCH 3/4] throw, changelog --- CHANGELOG.md | 2 ++ src/runtime/store/index.ts | 8 ++++---- test/store/index.js | 36 +++++++++--------------------------- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e76c170545..3ad9a97b4326 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,11 @@ * **breaking** Stricter types for `onMount` - now throws a type error when returning a function asynchronously to catch potential mistakes around callback functions (see PR for migration instructions) ([#8136](https://github.com/sveltejs/svelte/pull/8136)) * **breaking** Overhaul and drastically improve creating custom elements with Svelte (see PR for list of changes and migration instructions) ([#8457](https://github.com/sveltejs/svelte/pull/8457)) * **breaking** Deprecate `SvelteComponentTyped`, use `SvelteComponent` instead ([#8512](https://github.com/sveltejs/svelte/pull/8512)) +* **breaking** Error on falsy values instead of stores passed to `derived` ([#7947](https://github.com/sveltejs/svelte/pull/7947)) * Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391)) * Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251)) * Bind `null` option and input values consistently ([#8312](https://github.com/sveltejs/svelte/issues/8312)) +* Allow `$store` to be used with changing values including nullish values ([#7555](https://github.com/sveltejs/svelte/issues/7555)) ## Unreleased (3.0) diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 13131d4cc0e4..9a4ad40da048 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -164,10 +164,10 @@ export function derived( export function derived(stores: Stores, fn: Function, initial_value?: T): Readable { const single = !Array.isArray(stores); - const stores_array: Array> = single - // Fall back to readable() for falsy stores in array, else derived store will be forever pending - ? [(stores || readable()) as Readable] - : (stores as Array>).map(store => store || readable()); + const stores_array: Array> = single ? [stores as Readable] : stores as Array>; + if (!stores_array.every(Boolean)) { + throw new Error('derived() expects stores as input, got a falsy value') + } const auto = fn.length < 2; diff --git a/test/store/index.js b/test/store/index.js index d9c806a48677..7487903ed42c 100644 --- a/test/store/index.js +++ b/test/store/index.js @@ -429,37 +429,19 @@ describe('store', () => { assert.equal(b_started, false); }); - it('works with undefined stores #1', () => { - const a = derived(null, (n) => { - return n; + it('errors on undefined stores #1', () => { + assert.throws(() => { + derived(null, (n) => n); }); - const values = []; - const unsubscribe = a.subscribe((value) => values.push(value)); - unsubscribe(); - assert.deepEqual(values, [undefined]); }); - it('works with undefined stores #2', () => { - const a = writable(1); - const b = derived([a, null, undefined], ([n, un1, un2]) => { - assert.equal(un1, undefined); - assert.equal(un2, undefined); - return n * 2; - }); - - const values = []; - - const unsubscribe = b.subscribe(value => { - values.push(value); + it('errors on undefined stores #2', () => { + assert.throws(() => { + const a = writable(1); + derived([a, null, undefined], ([n]) => { + return n * 2; + }); }); - - a.set(2); - assert.deepEqual(values, [2, 4]); - - unsubscribe(); - - a.set(3); - assert.deepEqual(values, [2, 4]); }); }); From fc69b4b1c087c3799ae83e1c8427347d754d518b Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 4 May 2023 15:13:49 +0200 Subject: [PATCH 4/4] lint --- src/runtime/store/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 9a4ad40da048..319f5a4d2e64 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -166,7 +166,7 @@ export function derived(stores: Stores, fn: Function, initial_value?: T): Rea const single = !Array.isArray(stores); const stores_array: Array> = single ? [stores as Readable] : stores as Array>; if (!stores_array.every(Boolean)) { - throw new Error('derived() expects stores as input, got a falsy value') + throw new Error('derived() expects stores as input, got a falsy value'); } const auto = fn.length < 2;