diff --git a/.eslintignore b/.eslintignore index b5cb03ae6e..d123c10530 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,7 +2,6 @@ **/expected.js _output test/*/samples/*/output.js -node_modules # automatically generated internal_exports.ts diff --git a/.eslintrc.js b/.eslintrc.js index 946a157e40..a093de610b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,65 +1,6 @@ module.exports = { root: true, - rules: { - indent: 'off', - 'no-unused-vars': 'off', - semi: [2, 'always'], - 'keyword-spacing': [2, { before: true, after: true }], - 'space-before-blocks': [2, 'always'], - 'no-mixed-spaces-and-tabs': [2, 'smart-tabs'], - 'no-cond-assign': 0, - 'object-shorthand': [2, 'always'], - 'no-const-assign': 2, - 'no-class-assign': 2, - 'no-this-before-super': 2, - 'no-var': 2, - 'no-unreachable': 2, - 'valid-typeof': 2, - 'quote-props': [2, 'as-needed'], - 'one-var': [2, 'never'], - 'prefer-arrow-callback': 2, - 'prefer-const': [2, { destructuring: 'all' }], - 'arrow-spacing': 2, - 'no-inner-declarations': 0, - 'require-atomic-updates': 'off', - '@typescript-eslint/indent': 'off', - '@typescript-eslint/camelcase': 'off', - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/array-type': ['error', 'array-simple'], - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/explicit-member-accessibility': 'off', - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_' - } - ], - '@typescript-eslint/no-object-literal-type-assertion': 'off', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/prefer-interface': 'off' - }, - globals: { - globalThis: false - }, - env: { - es6: true, - browser: true, - node: true, - mocha: true - }, - extends: [ - 'eslint:recommended', - 'plugin:import/errors', - 'plugin:import/warnings', - 'plugin:import/typescript', - 'plugin:@typescript-eslint/recommended' - ], - parserOptions: { - ecmaVersion: 9, - sourceType: 'module' - }, - plugins: ['svelte3'], + extends: '@sveltejs', settings: { 'import/core-modules': [ 'svelte', @@ -69,20 +10,5 @@ module.exports = { 'estree' ], 'svelte3/compiler': require('./compiler') - }, - overrides: [ - { - files: ['*.js'], - rules: { - '@typescript-eslint/no-var-requires': 'off' - } - }, - { - files: ['*.svelte'], - processor: 'svelte3/svelte3', - rules: { - '@typescript-eslint/indent': 'off' - } - } - ] + } }; diff --git a/CHANGELOG.md b/CHANGELOG.md index 73bc68d72f..5672da69b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Svelte changelog +## 3.23.1 + +* Fix checkbox `bind:group` when multiple options have the same value ([#4397](https://github.com/sveltejs/svelte/issues/4397)) +* Fix `bind:this` to the value of an `{#each}` block ([#4517](https://github.com/sveltejs/svelte/issues/4517)) +* Fix reactivity when assigning to contextual `{#each}` variable ([#4574](https://github.com/sveltejs/svelte/issues/4574), [#4744](https://github.com/sveltejs/svelte/issues/4744)) +* Fix binding to contextual `{#each}` values that shadow outer names ([#4757](https://github.com/sveltejs/svelte/issues/4757)) +* Work around EdgeHTML DOM issue when removing attributes during hydration ([#4911](https://github.com/sveltejs/svelte/pull/4911)) +* Throw CSS parser error when `:global()` does not contain a selector ([#4930](https://github.com/sveltejs/svelte/issues/4930)) + ## 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`; } // a
+ b
+ c
+ d
+ +
+ + a
+ b
+ c
+ d
+ `, + + async test({ assert, component, target, window }) { + const inputs = target.querySelectorAll("input"); + const p = target.querySelector("p"); + + assert.equal(inputs[0].checked, false); + assert.equal(inputs[1].checked, false); + assert.equal(inputs[2].checked, false); + assert.equal(inputs[3].checked, false); + + assert.equal(inputs[4].checked, false); + assert.equal(inputs[5].checked, false); + assert.equal(inputs[6].checked, false); + assert.equal(inputs[7].checked, false); + + const event = new window.Event("change"); + + inputs[0].checked = true; + await inputs[0].dispatchEvent(event); + + assert.htmlEqual(p.innerHTML, `Checked: a`); + + assert.equal(inputs[0].checked, true); + assert.equal(inputs[1].checked, false); + assert.equal(inputs[2].checked, false); + assert.equal(inputs[3].checked, false); + + assert.equal(inputs[4].checked, true); + assert.equal(inputs[5].checked, false); + assert.equal(inputs[6].checked, false); + assert.equal(inputs[7].checked, false); + + inputs[3].checked = true; + await inputs[3].dispatchEvent(event); + + assert.htmlEqual(p.innerHTML, `Checked: a,d`); + + assert.equal(inputs[0].checked, true); + assert.equal(inputs[1].checked, false); + assert.equal(inputs[2].checked, false); + assert.equal(inputs[3].checked, true); + + assert.equal(inputs[4].checked, true); + assert.equal(inputs[5].checked, false); + assert.equal(inputs[6].checked, false); + assert.equal(inputs[7].checked, true); + + inputs[4].checked = false; + await inputs[4].dispatchEvent(event); + + assert.htmlEqual(p.innerHTML, `Checked: d`); + + assert.equal(inputs[0].checked, false); + assert.equal(inputs[1].checked, false); + assert.equal(inputs[2].checked, false); + assert.equal(inputs[3].checked, true); + + assert.equal(inputs[4].checked, false); + assert.equal(inputs[5].checked, false); + assert.equal(inputs[6].checked, false); + assert.equal(inputs[7].checked, true); + }, +}; diff --git a/test/runtime/samples/binding-input-group-duplicate-value/main.svelte b/test/runtime/samples/binding-input-group-duplicate-value/main.svelte new file mode 100644 index 0000000000..153e559d23 --- /dev/null +++ b/test/runtime/samples/binding-input-group-duplicate-value/main.svelte @@ -0,0 +1,19 @@ + + +

Checked: {foo}

+ +
+ +a
+b
+c
+d
+ +
+ +a
+b
+c
+d
\ No newline at end of file diff --git a/test/runtime/samples/each-block-scope-shadow-bind-2/_config.js b/test/runtime/samples/each-block-scope-shadow-bind-2/_config.js new file mode 100644 index 0000000000..00e436a5aa --- /dev/null +++ b/test/runtime/samples/each-block-scope-shadow-bind-2/_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-2/main.svelte b/test/runtime/samples/each-block-scope-shadow-bind-2/main.svelte new file mode 100644 index 0000000000..f5bff01e6c --- /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 0000000000..e1a385acaf --- /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 0000000000..2e8fe5e591 --- /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 0000000000..3dffa560da --- /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 0000000000..bc4f172dd0 --- /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 0000000000..00e436a5aa --- /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 0000000000..f3471e179f --- /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 diff --git a/test/runtime/samples/each-blocks-assignment-2/_config.js b/test/runtime/samples/each-blocks-assignment-2/_config.js new file mode 100644 index 0000000000..e5fe84ea7c --- /dev/null +++ b/test/runtime/samples/each-blocks-assignment-2/_config.js @@ -0,0 +1,20 @@ +export default { + html: ` + foo + + `, + async test({ assert, component, target, window }) { + const button = target.querySelector("button"); + + const clickEvent = new window.MouseEvent("click"); + await button.dispatchEvent(clickEvent); + + assert.htmlEqual( + target.innerHTML, + ` + bar + + ` + ); + }, +}; diff --git a/test/runtime/samples/each-blocks-assignment-2/main.svelte b/test/runtime/samples/each-blocks-assignment-2/main.svelte new file mode 100644 index 0000000000..5ef3ae83ac --- /dev/null +++ b/test/runtime/samples/each-blocks-assignment-2/main.svelte @@ -0,0 +1,12 @@ + + +{#each arr as o} + {o.prop} + +{/each} \ No newline at end of file diff --git a/test/runtime/samples/each-blocks-assignment/_config.js b/test/runtime/samples/each-blocks-assignment/_config.js new file mode 100644 index 0000000000..ac07ad1656 --- /dev/null +++ b/test/runtime/samples/each-blocks-assignment/_config.js @@ -0,0 +1,97 @@ +export default { + html: ` + + 1 + + 2 + + 3 + + `, + async test({ assert, component, target, window }) { + let [incrementBtn, ...buttons] = target.querySelectorAll("button"); + + const clickEvent = new window.MouseEvent("click"); + await buttons[0].dispatchEvent(clickEvent); + + assert.htmlEqual( + target.innerHTML, + ` + + 2 + + 2 + + 3 + + ` + ); + + await buttons[0].dispatchEvent(clickEvent); + + assert.htmlEqual( + target.innerHTML, + ` + + 4 + + 2 + + 3 + + ` + ); + + await buttons[2].dispatchEvent(clickEvent); + await buttons[2].dispatchEvent(clickEvent); + + assert.htmlEqual( + target.innerHTML, + ` + + 4 + + 2 + + 12 + + ` + ); + + await incrementBtn.dispatchEvent(clickEvent); + + assert.htmlEqual( + target.innerHTML, + ` + + 4 + + 2 + + 12 + + 4 + + ` + ); + + [incrementBtn, ...buttons] = target.querySelectorAll("button"); + + await buttons[3].dispatchEvent(clickEvent); + + assert.htmlEqual( + target.innerHTML, + ` + + 4 + + 2 + + 12 + + 8 + + ` + ); + }, +}; diff --git a/test/runtime/samples/each-blocks-assignment/main.svelte b/test/runtime/samples/each-blocks-assignment/main.svelte new file mode 100644 index 0000000000..f74bffbe04 --- /dev/null +++ b/test/runtime/samples/each-blocks-assignment/main.svelte @@ -0,0 +1,13 @@ + + + +{#each arr as o} + {o} + +{/each} \ No newline at end of file