From 562f23a87bb8412d1a81117de4ac1ee1e611ffb6 Mon Sep 17 00:00:00 2001 From: Richard Harris Date: Mon, 6 May 2019 06:17:26 -0400 Subject: [PATCH] invalidate dependencies of reactive declarations - fixes #2444 --- src/compile/Component.ts | 22 ++- .../_config.js | 125 ++++++++++++++++++ .../main.svelte | 25 ++++ 3 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 test/runtime/samples/binding-input-text-contextual-reactive/_config.js create mode 100644 test/runtime/samples/binding-input-text-contextual-reactive/main.svelte diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 7d4aa72f03..1d142c7d78 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -753,18 +753,34 @@ export default class Component { }); } - invalidate(name, value = name) { + invalidate(name, value) { const variable = this.var_lookup.get(name); if (variable && (variable.subscribable && variable.reassigned)) { - return `$$subscribe_${name}(), $$invalidate('${name}', ${value})`; + return `$$subscribe_${name}(), $$invalidate('${name}', ${value || name})`; } if (name[0] === '$' && name[1] !== '$') { return `${name.slice(1)}.set(${name})` } - return `$$invalidate('${name}', ${value})`; + if (value) { + return `$$invalidate('${name}', ${value})`; + } + + // if this is a reactive declaration, invalidate dependencies recursively + const deps = new Set([name]); + + deps.forEach(name => { + const reactive_declarations = this.reactive_declarations.filter(x => x.assignees.has(name)); + reactive_declarations.forEach(declaration => { + declaration.dependencies.forEach(name => { + deps.add(name); + }); + }); + }); + + return Array.from(deps).map(n => `$$invalidate('${n}', ${n})`).join(', '); } rewrite_props(get_insert: (variable: Var) => string) { diff --git a/test/runtime/samples/binding-input-text-contextual-reactive/_config.js b/test/runtime/samples/binding-input-text-contextual-reactive/_config.js new file mode 100644 index 0000000000..6fdfc4be52 --- /dev/null +++ b/test/runtime/samples/binding-input-text-contextual-reactive/_config.js @@ -0,0 +1,125 @@ +export default { + props: { + items: [ + { done: false, text: 'one' }, + { done: true, text: 'two' }, + { done: false, text: 'three' } + ] + }, + + html: ` +
+ +

one

+
+
+ +

two

+
+
+ +

three

+
+ +

remaining:one / done:two / remaining:three

+ `, + + ssrHtml: ` +
+ +

one

+
+
+ +

two

+
+
+ +

three

+
+ +

remaining:one / done:two / remaining:three

+ `, + + async test({ assert, component, target, window }) { + function set_text(i, text) { + const input = target.querySelectorAll('input[type="text"]')[i]; + input.value = text; + input.dispatchEvent(new window.Event('input')); + } + + function set_done(i, done) { + const input = target.querySelectorAll('input[type="checkbox"]')[i]; + input.checked = done; + input.dispatchEvent(new window.Event('change')); + } + + component.filter = 'remaining'; + + assert.htmlEqual(target.innerHTML, ` +
+ +

one

+
+
+ +

three

+
+ +

remaining:one / done:two / remaining:three

+ `); + + await set_text(1, 'four'); + + assert.htmlEqual(target.innerHTML, ` +
+ +

one

+
+
+ +

four

+
+ +

remaining:one / done:two / remaining:four

+ `); + + assert.deepEqual(component.items, [ + { done: false, text: 'one' }, + { done: true, text: 'two' }, + { done: false, text: 'four' } + ]); + + await set_done(0, true); + + assert.htmlEqual(target.innerHTML, ` +
+ +

four

+
+ +

done:one / done:two / remaining:four

+ `); + + assert.deepEqual(component.items, [ + { done: true, text: 'one' }, + { done: true, text: 'two' }, + { done: false, text: 'four' } + ]); + + component.filter = 'done'; + + assert.htmlEqual(target.innerHTML, ` +
+ +

one

+
+
+ +

two

+
+ +

done:one / done:two / remaining:four

+ `); + }, +}; diff --git a/test/runtime/samples/binding-input-text-contextual-reactive/main.svelte b/test/runtime/samples/binding-input-text-contextual-reactive/main.svelte new file mode 100644 index 0000000000..7e10199530 --- /dev/null +++ b/test/runtime/samples/binding-input-text-contextual-reactive/main.svelte @@ -0,0 +1,25 @@ + + +{#each filtered as item} +
+ + +

{item.text}

+
+{/each} + +

{summary}

\ No newline at end of file