mirror of https://github.com/sveltejs/svelte
The readonly dev time validation results in false-negative object equality checks: The original object is proxified and thus comparisons could be between the unproxified and proxified version, which will always return false. Fixes #10372 There's also the problem that an object could be passed into a component but then passed upwards into shared state. At that point, it should no longer be readonly, but it's not possible to statically analyze these points. Fixes #10372 Lastly, the each block logic mutates an internal array and that also throws errors with the readonly logic. Fixes #10037remove-readonly-check
parent
d08e05bf7f
commit
f0d3740e30
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: remove readonly validation as it results in false-negative object equality checks
|
@ -0,0 +1,15 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
html: `<button>add</button> <p>1</p><p>1</p><p>1</p>`,
|
||||||
|
|
||||||
|
async test({ assert, target }) {
|
||||||
|
const btn = target.querySelector('button');
|
||||||
|
|
||||||
|
await btn?.click();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`<button>add</button> <p>1</p><p>2</p><p>1</p><p>2</p><p>1</p><p>2</p>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
let { array } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each array as number}
|
||||||
|
<p>{number.v}</p>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#each array as number (number)}
|
||||||
|
<p>{number.v}</p>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#each array as number (number.v)}
|
||||||
|
<p>{number.v}</p>
|
||||||
|
{/each}
|
@ -0,0 +1,12 @@
|
|||||||
|
<script>
|
||||||
|
import Child from './child.svelte';
|
||||||
|
|
||||||
|
let array = $state([{v: 1}]);
|
||||||
|
|
||||||
|
const addNew = () => {
|
||||||
|
array.push({v: 2})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={addNew}>add</button>
|
||||||
|
<Child {array} />
|
@ -0,0 +1,45 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
html: `
|
||||||
|
<button>a true</button><button>b true</button>
|
||||||
|
<button>a true</button><button>b true</button>
|
||||||
|
<button>a true</button><button>b true</button>
|
||||||
|
`,
|
||||||
|
|
||||||
|
async test({ assert, target }) {
|
||||||
|
let [btn1, _btn2, btn3, _btn4, btn5] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
await btn1.click();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>a+ true</button><button>b true</button>
|
||||||
|
<button>a+ true</button><button>b true</button>
|
||||||
|
<button>a+ true</button><button>b true</button>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
[btn1, _btn2, btn3, _btn4, btn5] = target.querySelectorAll('button');
|
||||||
|
await btn3.click();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>a++ true</button><button>b true</button>
|
||||||
|
<button>a++ true</button><button>b true</button>
|
||||||
|
<button>a++ true</button><button>b true</button>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
[btn1, _btn2, btn3, _btn4, btn5] = target.querySelectorAll('button');
|
||||||
|
await btn5.click();
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>a+++ true</button><button>b true</button>
|
||||||
|
<button>a+++ true</button><button>b true</button>
|
||||||
|
<button>a+++ true</button><button>b true</button>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
<script>
|
||||||
|
let {item, items, onclick} = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button {onclick}>
|
||||||
|
{item.name} {items.includes(item)}
|
||||||
|
</button>
|
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
import Item from './item.svelte'
|
||||||
|
|
||||||
|
let items = $state([{name: 'a'}, {name: 'b'}]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- test that each block doesn't mess with item identity -->
|
||||||
|
|
||||||
|
{#each items as item (item)}
|
||||||
|
<Item {item} {items} onclick={() => item.name = item.name + '+'} />
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#each items as item (item.name)}
|
||||||
|
<Item {item} {items} onclick={() => {console.log('hello'); item.name = item.name + '+'}} />
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#each items as item}
|
||||||
|
<Item {item} {items} onclick={() => item.name = item.name + '+'} />
|
||||||
|
{/each}
|
@ -1,8 +0,0 @@
|
|||||||
<script>
|
|
||||||
/** @type {{ object: { count: number }}} */
|
|
||||||
let { object } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button onclick={() => object = { count: object.count + 1 } }>
|
|
||||||
clicks: {object.count}
|
|
||||||
</button>
|
|
@ -1,22 +0,0 @@
|
|||||||
import { test } from '../../test';
|
|
||||||
|
|
||||||
// Tests that readonly bails on setters/classes
|
|
||||||
export default test({
|
|
||||||
html: `<button>clicks: 0</button><button>clicks: 0</button>`,
|
|
||||||
|
|
||||||
compileOptions: {
|
|
||||||
dev: true
|
|
||||||
},
|
|
||||||
|
|
||||||
async test({ assert, target }) {
|
|
||||||
const [btn1, btn2] = target.querySelectorAll('button');
|
|
||||||
|
|
||||||
await btn1.click();
|
|
||||||
await btn2.click();
|
|
||||||
assert.htmlEqual(target.innerHTML, `<button>clicks: 1</button><button>clicks: 1</button>`);
|
|
||||||
|
|
||||||
await btn1.click();
|
|
||||||
await btn2.click();
|
|
||||||
assert.htmlEqual(target.innerHTML, `<button>clicks: 2</button><button>clicks: 2</button>`);
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,25 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Counter from './Counter.svelte';
|
|
||||||
|
|
||||||
function createCounter() {
|
|
||||||
let count = $state(0)
|
|
||||||
return {
|
|
||||||
get count() {
|
|
||||||
return count;
|
|
||||||
},
|
|
||||||
set count(upd) {
|
|
||||||
count = upd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CounterClass {
|
|
||||||
count = $state(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const counterSetter = createCounter();
|
|
||||||
const counterClass = new CounterClass();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Counter object={counterSetter} />
|
|
||||||
<Counter object={counterClass} />
|
|
@ -1,8 +0,0 @@
|
|||||||
<script>
|
|
||||||
/** @type {{ object?: { count: number }}} */
|
|
||||||
let { object = { count: 0 } } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button onclick={() => object = { count: object.count + 1 } }>
|
|
||||||
clicks: {object.count}
|
|
||||||
</button>
|
|
@ -1,19 +0,0 @@
|
|||||||
import { test } from '../../test';
|
|
||||||
|
|
||||||
export default test({
|
|
||||||
html: `<button>clicks: 0</button>`,
|
|
||||||
|
|
||||||
compileOptions: {
|
|
||||||
dev: true
|
|
||||||
},
|
|
||||||
|
|
||||||
async test({ assert, target }) {
|
|
||||||
const btn = target.querySelector('button');
|
|
||||||
|
|
||||||
await btn?.click();
|
|
||||||
assert.htmlEqual(target.innerHTML, `<button>clicks: 1</button>`);
|
|
||||||
|
|
||||||
await btn?.click();
|
|
||||||
assert.htmlEqual(target.innerHTML, `<button>clicks: 2</button>`);
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,5 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Counter from './Counter.svelte';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Counter />
|
|
@ -1,8 +0,0 @@
|
|||||||
<script>
|
|
||||||
/** @type {{ object?: { count: number }}} */
|
|
||||||
let { object = { count: 0 } } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button onclick={() => object.count += 1}>
|
|
||||||
clicks: {object.count}
|
|
||||||
</button>
|
|
@ -1,19 +0,0 @@
|
|||||||
import { test } from '../../test';
|
|
||||||
|
|
||||||
export default test({
|
|
||||||
html: `<button>clicks: 0</button>`,
|
|
||||||
|
|
||||||
compileOptions: {
|
|
||||||
dev: true
|
|
||||||
},
|
|
||||||
|
|
||||||
async test({ assert, target }) {
|
|
||||||
const btn = target.querySelector('button');
|
|
||||||
await btn?.click();
|
|
||||||
|
|
||||||
assert.htmlEqual(target.innerHTML, `<button>clicks: 0</button>`);
|
|
||||||
},
|
|
||||||
|
|
||||||
runtime_error:
|
|
||||||
'Non-bound props cannot be mutated — to make the `count` settable, ensure the object it is used within is bound as a prop `bind:<prop>={...}`. Fallback values can never be mutated.'
|
|
||||||
});
|
|
@ -1,5 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Counter from './Counter.svelte';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Counter />
|
|
@ -1,8 +0,0 @@
|
|||||||
<script>
|
|
||||||
/** @type {{ object: { count: number }}} */
|
|
||||||
let { object } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<button onclick={() => object.count += 1}>
|
|
||||||
clicks: {object.count}
|
|
||||||
</button>
|
|
@ -1,19 +0,0 @@
|
|||||||
import { test } from '../../test';
|
|
||||||
|
|
||||||
export default test({
|
|
||||||
html: `<button>clicks: 0</button>`,
|
|
||||||
|
|
||||||
compileOptions: {
|
|
||||||
dev: true
|
|
||||||
},
|
|
||||||
|
|
||||||
async test({ assert, target }) {
|
|
||||||
const btn = target.querySelector('button');
|
|
||||||
await btn?.click();
|
|
||||||
|
|
||||||
assert.htmlEqual(target.innerHTML, `<button>clicks: 0</button>`);
|
|
||||||
},
|
|
||||||
|
|
||||||
runtime_error:
|
|
||||||
'Non-bound props cannot be mutated — to make the `count` settable, ensure the object it is used within is bound as a prop `bind:<prop>={...}`. Fallback values can never be mutated.'
|
|
||||||
});
|
|
@ -1,7 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Counter from './Counter.svelte';
|
|
||||||
|
|
||||||
const object = $state({ count: 0 });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Counter {object} />
|
|
Loading…
Reference in new issue