fix: keep default values of props a proxy after reassignment (#11860)

* fix: keep default values of props a proxy after reassignment

* fix: make this work for non bindable props too

* chore: more comprehensive test

* chore: cast away

* chore: better variable name and check

* chore: fix lint
pull/11889/head
Paolo Ricciuti 7 months ago committed by GitHub
parent 0a7de87eb5
commit 5eb8641ac7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: keep default values of props a proxy after reassignment

@ -292,7 +292,13 @@ export function serialize_set_binding(node, context, fallback, options) {
const serialize = () => {
if (left === node.left) {
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') {
const is_initial_proxy =
binding.initial !== null &&
should_proxy_or_freeze(
/**@type {import("estree").Expression}*/ (binding.initial),
context.state.scope
);
if ((binding.kind === 'prop' || binding.kind === 'bindable_prop') && !is_initial_proxy) {
return b.call(left, value);
} else if (is_store) {
return b.call('$.store_set', serialize_get_binding(b.id(left_name), state), value);
@ -318,6 +324,18 @@ export function serialize_set_binding(node, context, fallback, options) {
? b.call('$.freeze', value)
: value
);
} else if (
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
is_initial_proxy
) {
call = b.call(
left,
context.state.analysis.runes &&
!options?.skip_proxy_and_freeze &&
should_proxy_or_freeze(value, context.state.scope)
? serialize_proxy_reassignment(value, left_name, state)
: value
);
} else {
call = b.call('$.set', b.id(left_name), value);
}

@ -1,12 +1,20 @@
<script>
/** @type {{ object?: { count: number }}} */
let { object = $bindable({ count: 0 }) } = $props();
/** @type {{ object?: { count: number }, non_bindable?: { count: number }}} */
let { object = $bindable({ count: 0 }), non_bindable = { count: 0 } } = $props();
</script>
<button onclick={() => object.count += 1}>
<button onclick={() => (object.count += 1)}>
mutate: {object.count}
</button>
<button onclick={() => object = { count: object.count + 1 } }>
<button onclick={() => (object = { count: object.count + 1 })}>
reassign: {object.count}
</button>
<button onclick={() => (non_bindable.count += 1)}>
mutate: {non_bindable.count}
</button>
<button onclick={() => (non_bindable = { count: non_bindable.count + 1 })}>
reassign: {non_bindable.count}
</button>

@ -5,10 +5,12 @@ export default test({
html: `
<button>mutate: 0</button>
<button>reassign: 0</button>
<button>mutate: 0</button>
<button>reassign: 0</button>
`,
async test({ assert, target }) {
const [btn1, btn2] = target.querySelectorAll('button');
const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button');
flushSync(() => {
btn1?.click();
@ -19,6 +21,8 @@ export default test({
`
<button>mutate: 1</button>
<button>reassign: 1</button>
<button>mutate: 0</button>
<button>reassign: 0</button>
`
);
@ -31,6 +35,64 @@ export default test({
`
<button>mutate: 2</button>
<button>reassign: 2</button>
<button>mutate: 0</button>
<button>reassign: 0</button>
`
);
flushSync(() => {
btn1?.click();
});
assert.htmlEqual(
target.innerHTML,
`
<button>mutate: 3</button>
<button>reassign: 3</button>
<button>mutate: 0</button>
<button>reassign: 0</button>
`
);
flushSync(() => {
btn3?.click();
});
assert.htmlEqual(
target.innerHTML,
`
<button>mutate: 3</button>
<button>reassign: 3</button>
<button>mutate: 1</button>
<button>reassign: 1</button>
`
);
flushSync(() => {
btn4?.click();
});
assert.htmlEqual(
target.innerHTML,
`
<button>mutate: 3</button>
<button>reassign: 3</button>
<button>mutate: 2</button>
<button>reassign: 2</button>
`
);
flushSync(() => {
btn3?.click();
});
assert.htmlEqual(
target.innerHTML,
`
<button>mutate: 3</button>
<button>reassign: 3</button>
<button>mutate: 3</button>
<button>reassign: 3</button>
`
);
}

Loading…
Cancel
Save