mirror of https://github.com/sveltejs/svelte
fix: better ownership mutation validation (#10673)
- widen ownership when getContext is called, part of #10649 - widen ownership when assigning one proxy to another - skip first 4 stack trace entries and bail if first after that is not a module, hinting at a mutation encapsulated in a .svelte.js file; part of #10649pull/10679/head
parent
74f8e261c6
commit
5768ac3027
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"svelte": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: better ownership mutation validation
|
@ -0,0 +1,37 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
/** @type {typeof console.warn} */
|
||||||
|
let warn;
|
||||||
|
|
||||||
|
/** @type {any[]} */
|
||||||
|
let warnings = [];
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
html: `<button>[]</button>`,
|
||||||
|
|
||||||
|
compileOptions: {
|
||||||
|
dev: true
|
||||||
|
},
|
||||||
|
|
||||||
|
before_test: () => {
|
||||||
|
warn = console.warn;
|
||||||
|
|
||||||
|
console.warn = (...args) => {
|
||||||
|
warnings.push(...args);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
after_test: () => {
|
||||||
|
console.warn = warn;
|
||||||
|
warnings = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
async test({ assert, target }) {
|
||||||
|
const btn = target.querySelector('button');
|
||||||
|
await btn?.click();
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `<button>[foo]</button>`);
|
||||||
|
|
||||||
|
assert.deepEqual(warnings, [], 'expected getContext to have widened ownership');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
import { setContext } from 'svelte';
|
||||||
|
import Sub from './sub.svelte';
|
||||||
|
|
||||||
|
let list = $state([]);
|
||||||
|
setContext('list', list);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Sub />
|
@ -0,0 +1,7 @@
|
|||||||
|
<script>
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
|
const list = getContext('list');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={() => list.push('foo')}>[{list.join(',')}]</button>
|
@ -0,0 +1,32 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
/** @type {typeof console.warn} */
|
||||||
|
let warn;
|
||||||
|
|
||||||
|
/** @type {any[]} */
|
||||||
|
let warnings = [];
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
compileOptions: {
|
||||||
|
dev: true
|
||||||
|
},
|
||||||
|
|
||||||
|
before_test: () => {
|
||||||
|
warn = console.warn;
|
||||||
|
|
||||||
|
console.warn = (...args) => {
|
||||||
|
warnings.push(...args);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
after_test: () => {
|
||||||
|
console.warn = warn;
|
||||||
|
warnings = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
async test({ assert, target }) {
|
||||||
|
const btn = target.querySelector('button');
|
||||||
|
await btn?.click();
|
||||||
|
assert.deepEqual(warnings, []);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,11 @@
|
|||||||
|
<script>
|
||||||
|
import { global } from './state.svelte.js';
|
||||||
|
import Sub from './sub.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Sub />
|
||||||
|
<!-- it's important _NOT_ to read global.a.b in the template,
|
||||||
|
else the proxy would set up the structure with its own component context already -->
|
||||||
|
<button onclick={() => global.increment_a_b()}>
|
||||||
|
click me
|
||||||
|
</button>
|
@ -0,0 +1,13 @@
|
|||||||
|
class Global {
|
||||||
|
state = $state({});
|
||||||
|
|
||||||
|
add_a(a) {
|
||||||
|
this.state.a = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
increment_a_b() {
|
||||||
|
this.state.a.b++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const global = new Global();
|
@ -0,0 +1,8 @@
|
|||||||
|
<script>
|
||||||
|
import { global } from "./state.svelte";
|
||||||
|
|
||||||
|
// The real world use case would be someone manipulating state locally, for example form state,
|
||||||
|
// and then push the values to a global state for everyone else to see / possibly mutate.
|
||||||
|
const local_soon_global = $state({ b: 0 });
|
||||||
|
global.add_a(local_soon_global);
|
||||||
|
</script>
|
@ -0,0 +1,39 @@
|
|||||||
|
import { tick } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
/** @type {typeof console.warn} */
|
||||||
|
let warn;
|
||||||
|
|
||||||
|
/** @type {any[]} */
|
||||||
|
let warnings = [];
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
compileOptions: {
|
||||||
|
dev: true
|
||||||
|
},
|
||||||
|
|
||||||
|
before_test: () => {
|
||||||
|
warn = console.warn;
|
||||||
|
|
||||||
|
console.warn = (...args) => {
|
||||||
|
warnings.push(...args);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
after_test: () => {
|
||||||
|
console.warn = warn;
|
||||||
|
warnings = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
async test({ assert, target }) {
|
||||||
|
const [btn1, btn2] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
await btn1.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(warnings.length, 0);
|
||||||
|
|
||||||
|
await btn2.click();
|
||||||
|
await tick();
|
||||||
|
assert.deepEqual(warnings.length, 1);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Sub from './sub.svelte';
|
||||||
|
import { create_my_state } from './state.svelte';
|
||||||
|
|
||||||
|
const myState = create_my_state();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Sub count={myState.my_state} inc={myState.inc} />
|
@ -0,0 +1,14 @@
|
|||||||
|
export function create_my_state() {
|
||||||
|
const my_state = $state({
|
||||||
|
a: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
function inc() {
|
||||||
|
my_state.a++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
my_state,
|
||||||
|
inc
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
<script>
|
||||||
|
let { count, inc } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={inc}>{count.a} (ok)</button>
|
||||||
|
<button onclick={() => count.a++}>{count.a} (bad)</button>
|
Loading…
Reference in new issue