From bf6c74fb17ae7e2706599fe20057ad0d7e3fbb2c Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 8 Jun 2020 21:26:42 +0800 Subject: [PATCH] fix binding for each block local variable (#4861) --- CHANGELOG.md | 5 + .../render_dom/wrappers/Element/Binding.ts | 37 +-- .../render_dom/wrappers/Element/index.ts | 225 +++++++++--------- .../wrappers/InlineComponent/index.ts | 3 +- .../render_dom/wrappers/shared/bind_this.ts | 59 ++--- src/compiler/compile/utils/replace_object.ts | 14 ++ test/js/samples/loop-protect/expected.js | 3 +- .../each-block-scope-shadow-bind-2/_config.js | 23 ++ .../main.svelte | 10 + .../each-block-scope-shadow-bind-3/_config.js | 105 ++++++++ .../main.svelte | 14 ++ .../each-block-scope-shadow-bind-4/_config.js | 64 +++++ .../main.svelte | 14 ++ .../each-block-scope-shadow-bind/_config.js | 23 ++ .../each-block-scope-shadow-bind/main.svelte | 10 + 15 files changed, 437 insertions(+), 172 deletions(-) create mode 100644 src/compiler/compile/utils/replace_object.ts create mode 100644 test/runtime/samples/each-block-scope-shadow-bind-2/_config.js create mode 100644 test/runtime/samples/each-block-scope-shadow-bind-2/main.svelte create mode 100644 test/runtime/samples/each-block-scope-shadow-bind-3/_config.js create mode 100644 test/runtime/samples/each-block-scope-shadow-bind-3/main.svelte create mode 100644 test/runtime/samples/each-block-scope-shadow-bind-4/_config.js create mode 100644 test/runtime/samples/each-block-scope-shadow-bind-4/main.svelte create mode 100644 test/runtime/samples/each-block-scope-shadow-bind/_config.js create mode 100644 test/runtime/samples/each-block-scope-shadow-bind/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index 73bc68d72f1b..aa2e0339c087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Svelte changelog +## Unreleased + +* Fix `bind:this` to the value of an `{#each}` block ([#4517](https://github.com/sveltejs/svelte/issues/4517)) +* Fix binding to contextual `{#each}` values that shadow outer names ([#4757](https://github.com/sveltejs/svelte/issues/4757)) + ## 3.23.0 * Update ``? if (parent.node.name === 'select') { - parent.select_binding_dependencies = dependencies; + (parent as ElementWrapper).select_binding_dependencies = dependencies; dependencies.forEach((prop: string) => { parent.renderer.component.indirect_dependencies.set(prop, new Set()); }); @@ -207,7 +209,7 @@ export default class BindingWrapper { } function get_dom_updater( - element: ElementWrapper, + element: ElementWrapper | InlineComponentWrapper, binding: BindingWrapper ) { const { node } = element; @@ -270,21 +272,17 @@ function get_event_handler( contextual_dependencies: Set; lhs?: Node; } { - const value = get_value_from_dom(renderer, binding.parent, binding); - const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies); + const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies); const context = block.bindings.get(name); let set_store; if (context) { - const { object, property, modifier, store } = context; - - if (lhs.type === 'Identifier') { - lhs = modifier(x`${object}[${property}]`); - - contextual_dependencies.add(object.name); - contextual_dependencies.add(property.name); - } + const { object, property, store, snippet } = context; + lhs = replace_object(lhs, snippet); + contextual_dependencies.add(object.name); + contextual_dependencies.add(property.name); + contextual_dependencies.delete(name); if (store) { set_store = b`${store}.set(${`$${store}`});`; @@ -297,6 +295,8 @@ function get_event_handler( } } + const value = get_value_from_dom(renderer, binding.parent, binding); + const mutation = b` ${lhs} = ${value}; ${set_store} @@ -305,20 +305,21 @@ function get_event_handler( return { uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy mutation, - contextual_dependencies + contextual_dependencies, + lhs, }; } function get_value_from_dom( renderer: Renderer, - element: ElementWrapper, + element: ElementWrapper | InlineComponentWrapper, binding: BindingWrapper ) { const { node } = element; const { name } = binding.node; if (name === 'this') { - return x`$$node`; + return x`$$value`; } // + `, + ssrHtml: ` + Hello + + `, + async test({ assert, target, window }) { + const input = target.querySelector("input"); + input.value = "abcd"; + await input.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` + abcd + + ` + ); + }, +}; diff --git a/test/runtime/samples/each-block-scope-shadow-bind-2/main.svelte b/test/runtime/samples/each-block-scope-shadow-bind-2/main.svelte new file mode 100644 index 000000000000..f5bff01e6cc2 --- /dev/null +++ b/test/runtime/samples/each-block-scope-shadow-bind-2/main.svelte @@ -0,0 +1,10 @@ + + +{#each a as { a }} + {a} + +{/each} \ No newline at end of file diff --git a/test/runtime/samples/each-block-scope-shadow-bind-3/_config.js b/test/runtime/samples/each-block-scope-shadow-bind-3/_config.js new file mode 100644 index 000000000000..e1a385acaf4e --- /dev/null +++ b/test/runtime/samples/each-block-scope-shadow-bind-3/_config.js @@ -0,0 +1,105 @@ +export default { + html: ` +
+ Hello World + + +
+
+ Sapper App + + +
+ `, + + ssrHtml: ` +
+ Hello World + + +
+
+ Sapper App + + +
+ `, + async test({ assert, target, window }) { + const [input1, input2, input3, input4] = target.querySelectorAll("input"); + input1.value = "Awesome"; + await input1.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` +
+ Awesome World + + +
+
+ Sapper App + + +
+ ` + ); + + input2.value = "Svelte"; + await input2.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` +
+ Awesome Svelte + + +
+
+ Sapper App + + +
+ ` + ); + + input3.value = "Foo"; + await input3.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` +
+ Awesome Svelte + + +
+
+ Foo App + + +
+ ` + ); + + input4.value = "Bar"; + await input4.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` +
+ Awesome Svelte + + +
+
+ Foo Bar + + +
+ ` + ); + }, +}; diff --git a/test/runtime/samples/each-block-scope-shadow-bind-3/main.svelte b/test/runtime/samples/each-block-scope-shadow-bind-3/main.svelte new file mode 100644 index 000000000000..2e8fe5e59193 --- /dev/null +++ b/test/runtime/samples/each-block-scope-shadow-bind-3/main.svelte @@ -0,0 +1,14 @@ + + +{#each a as a} +
+ {a[0]} {a[1]} + + +
+{/each} \ No newline at end of file diff --git a/test/runtime/samples/each-block-scope-shadow-bind-4/_config.js b/test/runtime/samples/each-block-scope-shadow-bind-4/_config.js new file mode 100644 index 000000000000..3dffa560dad2 --- /dev/null +++ b/test/runtime/samples/each-block-scope-shadow-bind-4/_config.js @@ -0,0 +1,64 @@ +export default { + html: ` +
+ b: Hello + +
+ + `, + ssrHtml: ` +
+ b: Hello + +
+ + `, + async test({ assert, target, window }) { + const input = target.querySelector("input"); + const button = target.querySelector("button"); + + input.value = "Awesome"; + await input.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` +
+ b: Awesome + +
+ + ` + ); + + + await button.dispatchEvent(new window.MouseEvent("click")); + + assert.htmlEqual( + target.innerHTML, + ` +
+ c: World + +
+ + ` + ); + + assert.equal(input.value, 'World'); + + input.value = "Svelte"; + await input.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` +
+ c: Svelte + +
+ + ` + ); + }, +}; diff --git a/test/runtime/samples/each-block-scope-shadow-bind-4/main.svelte b/test/runtime/samples/each-block-scope-shadow-bind-4/main.svelte new file mode 100644 index 000000000000..bc4f172dd020 --- /dev/null +++ b/test/runtime/samples/each-block-scope-shadow-bind-4/main.svelte @@ -0,0 +1,14 @@ + + +{#each a as { a, key }} +
+ {key}: {a[key]} + +
+{/each} + + \ No newline at end of file diff --git a/test/runtime/samples/each-block-scope-shadow-bind/_config.js b/test/runtime/samples/each-block-scope-shadow-bind/_config.js new file mode 100644 index 000000000000..00e436a5aa00 --- /dev/null +++ b/test/runtime/samples/each-block-scope-shadow-bind/_config.js @@ -0,0 +1,23 @@ +export default { + html: ` + Hello + + `, + ssrHtml: ` + Hello + + `, + async test({ assert, target, window }) { + const input = target.querySelector("input"); + input.value = "abcd"; + await input.dispatchEvent(new window.Event("input")); + + assert.htmlEqual( + target.innerHTML, + ` + abcd + + ` + ); + }, +}; diff --git a/test/runtime/samples/each-block-scope-shadow-bind/main.svelte b/test/runtime/samples/each-block-scope-shadow-bind/main.svelte new file mode 100644 index 000000000000..f3471e179f3b --- /dev/null +++ b/test/runtime/samples/each-block-scope-shadow-bind/main.svelte @@ -0,0 +1,10 @@ + + +{#each a as a} + {a} + +{/each} \ No newline at end of file