mirror of https://github.com/sveltejs/svelte
commit
203c321b4b
@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
'svelte': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: don't set state withing `with_parent` in proxy
|
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: untrack `$inspect.with` and add check for unsafe mutation
|
@ -1,21 +1,14 @@
|
|||||||
/** @import { Expression } from 'estree' */
|
|
||||||
/** @import { AST } from '#compiler' */
|
/** @import { AST } from '#compiler' */
|
||||||
/** @import { ComponentContext } from '../types' */
|
/** @import { ComponentContext } from '../types' */
|
||||||
import * as b from '../../../../utils/builders.js';
|
import * as b from '../../../../utils/builders.js';
|
||||||
|
import { build_expression } from './shared/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AST.AttachTag} node
|
* @param {AST.AttachTag} node
|
||||||
* @param {ComponentContext} context
|
* @param {ComponentContext} context
|
||||||
*/
|
*/
|
||||||
export function AttachTag(node, context) {
|
export function AttachTag(node, context) {
|
||||||
context.state.init.push(
|
const expression = build_expression(context, node.expression, node.metadata.expression);
|
||||||
b.stmt(
|
context.state.init.push(b.stmt(b.call('$.attach', context.state.node, b.thunk(expression))));
|
||||||
b.call(
|
|
||||||
'$.attach',
|
|
||||||
context.state.node,
|
|
||||||
b.thunk(/** @type {Expression} */ (context.visit(node.expression)))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
context.next();
|
context.next();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
span[class].svelte-xyz { color: green }
|
||||||
|
div[style].svelte-xyz { color: green }
|
@ -0,0 +1,7 @@
|
|||||||
|
<span class:foo={true}></span>
|
||||||
|
<div style:--foo="bar"></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
span[class] { color: green }
|
||||||
|
div[style] { color: green }
|
||||||
|
</style>
|
@ -0,0 +1,20 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
warnings: [
|
||||||
|
{
|
||||||
|
code: 'css_unused_selector',
|
||||||
|
message: 'Unused CSS selector ".forth"\nhttps://svelte.dev/e/css_unused_selector',
|
||||||
|
start: {
|
||||||
|
line: 8,
|
||||||
|
column: 2,
|
||||||
|
character: 190
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: 8,
|
||||||
|
column: 8,
|
||||||
|
character: 196
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
.zero.first.svelte-xyz { color: green }
|
||||||
|
.second.svelte-xyz { color: green }
|
||||||
|
.third.svelte-xyz { color: green }
|
||||||
|
/* (unused) .forth { color: red }*/
|
@ -0,0 +1,9 @@
|
|||||||
|
<div class="zero" class:first={true}></div>
|
||||||
|
<div class:second={true} class:third={true}></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.zero.first { color: green }
|
||||||
|
.second { color: green }
|
||||||
|
.third { color: green }
|
||||||
|
.forth { color: red }
|
||||||
|
</style>
|
@ -0,0 +1,11 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
server_props: {
|
||||||
|
browser: false
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
browser: true
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1 @@
|
|||||||
|
<!--[--><div></div><!--]-->
|
@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
const { browser } = $props();
|
||||||
|
|
||||||
|
const attributes = {
|
||||||
|
"data-test": browser ? undefined : ""
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div {...attributes}></div>
|
@ -0,0 +1,12 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
test({ assert, target }) {
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `<div></div><button>inc</button> [0,0,0,0,0,0,0,0,0]`);
|
||||||
|
flushSync(() => button?.click());
|
||||||
|
assert.htmlEqual(target.innerHTML, `<div></div><button>inc</button> [0,0,0,0,0,0,0,0,0]`);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,45 @@
|
|||||||
|
<script>
|
||||||
|
let a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0, i = 0;
|
||||||
|
function inc() {
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
c++;
|
||||||
|
d++;
|
||||||
|
e++;
|
||||||
|
f++;
|
||||||
|
g++;
|
||||||
|
h++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if a = 0}{/if}
|
||||||
|
|
||||||
|
{#each [b = 0] as x}{x,''}{/each}
|
||||||
|
|
||||||
|
{#key c = 0}{/key}
|
||||||
|
|
||||||
|
{#await d = 0}{/await}
|
||||||
|
|
||||||
|
{#snippet snip()}{/snippet}
|
||||||
|
|
||||||
|
{@render (e = 0, snip)()}
|
||||||
|
|
||||||
|
{@html f = 0, ''}
|
||||||
|
|
||||||
|
<div {@attach !!(g = 0)}></div>
|
||||||
|
|
||||||
|
{#key 1}
|
||||||
|
{@const x = (h = 0)}
|
||||||
|
{x, ''}
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
{#if 1}
|
||||||
|
{@const x = (i = 0)}
|
||||||
|
{x, ''}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button on:click={inc}>inc</button>
|
||||||
|
[{a},{b},{c},{d},{e},{f},{g},{h},{i}]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
test({ assert, target }) {
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `<div></div><button data-foo="true">inc</button> 12 - 12`);
|
||||||
|
flushSync(() => button?.click());
|
||||||
|
assert.htmlEqual(target.innerHTML, `<div></div><button data-foo="true">inc</button> 13 - 12`);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,36 @@
|
|||||||
|
<script>
|
||||||
|
let count1 = 1;
|
||||||
|
let count2 = 1;
|
||||||
|
function fn(ret) {
|
||||||
|
if (count1 > 100) return ret;
|
||||||
|
count1++;
|
||||||
|
count2++;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fn(false)}{:else if fn(true)}{/if}
|
||||||
|
|
||||||
|
{#each fn([]) as x}{x, ''}{/each}
|
||||||
|
|
||||||
|
{#key fn(1)}{/key}
|
||||||
|
|
||||||
|
{#await fn(Promise.resolve())}{/await}
|
||||||
|
|
||||||
|
{#snippet snip()}{/snippet}
|
||||||
|
|
||||||
|
{@render fn(snip)()}
|
||||||
|
|
||||||
|
{@html fn('')}
|
||||||
|
|
||||||
|
<div {@attach fn(() => {})}></div>
|
||||||
|
|
||||||
|
{#key 1}
|
||||||
|
{@const x = fn('')}
|
||||||
|
{x}
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
<button data-foo={fn(true)} on:click={() => count1++}>{fn('inc')}</button>
|
||||||
|
{count1} - {count2}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
test({ assert, target }) {
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
|
||||||
|
assert.htmlEqual(target.innerHTML, `<div></div><button>inc</button> 10 - 10`);
|
||||||
|
flushSync(() => button?.click());
|
||||||
|
assert.htmlEqual(target.innerHTML, `<div></div><button>inc</button> 11 - 10`);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,46 @@
|
|||||||
|
<script>
|
||||||
|
let count1 = 1;
|
||||||
|
let count2 = 1;
|
||||||
|
function fn(ret) {
|
||||||
|
if (count1 > 100) return ret;
|
||||||
|
count1++;
|
||||||
|
count2++;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
get true() { return fn(true) },
|
||||||
|
get false() { return fn(false) },
|
||||||
|
get array() { return fn([]) },
|
||||||
|
get string() { return fn('') },
|
||||||
|
get promise() { return fn(Promise.resolve()) },
|
||||||
|
get snippet() { return fn(snip) },
|
||||||
|
get attachment() { return fn(() => {}) },
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if obj.false}{:else if obj.true}{/if}
|
||||||
|
|
||||||
|
{#each obj.array as x}{x, ''}{/each}
|
||||||
|
|
||||||
|
{#key obj.string}{/key}
|
||||||
|
|
||||||
|
{#await obj.promise}{/await}
|
||||||
|
|
||||||
|
{#snippet snip()}{/snippet}
|
||||||
|
|
||||||
|
{@render obj.snippet()}
|
||||||
|
|
||||||
|
{@html obj.string}
|
||||||
|
|
||||||
|
<div {@attach obj.attachment}></div>
|
||||||
|
|
||||||
|
{#key 1}
|
||||||
|
{@const x = obj.string}
|
||||||
|
{x}
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
<button on:click={() => count1++}>inc</button>
|
||||||
|
{count1} - {count2}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
/**
|
||||||
|
* Ensure that sorting an array inside an $effect works correctly
|
||||||
|
* and re-runs when the array changes (e.g., when items are added).
|
||||||
|
*/
|
||||||
|
test({ assert, target }) {
|
||||||
|
const button = target.querySelector('button');
|
||||||
|
|
||||||
|
// initial render — array should be sorted
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>add item</button>
|
||||||
|
<p>0</p>
|
||||||
|
<p>50</p>
|
||||||
|
<p>100</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
// add first item (20); effect should re-run and sort the array
|
||||||
|
flushSync(() => button?.click());
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>add item</button>
|
||||||
|
<p>0</p>
|
||||||
|
<p>20</p>
|
||||||
|
<p>50</p>
|
||||||
|
<p>100</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
// add second item (80); effect should re-run and sort the array
|
||||||
|
flushSync(() => button?.click());
|
||||||
|
|
||||||
|
assert.htmlEqual(
|
||||||
|
target.innerHTML,
|
||||||
|
`
|
||||||
|
<button>add item</button>
|
||||||
|
<p>0</p>
|
||||||
|
<p>20</p>
|
||||||
|
<p>50</p>
|
||||||
|
<p>80</p>
|
||||||
|
<p>100</p>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,21 @@
|
|||||||
|
<script>
|
||||||
|
let arr = $state([100, 0, 50]);
|
||||||
|
let nextValues = [20, 80];
|
||||||
|
let valueIndex = 0;
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
arr.sort((a, b) => a - b);
|
||||||
|
});
|
||||||
|
|
||||||
|
function addItem() {
|
||||||
|
if (valueIndex < nextValues.length) {
|
||||||
|
arr.push(nextValues[valueIndex]);
|
||||||
|
valueIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={addItem}>add item</button>
|
||||||
|
{#each arr as x}
|
||||||
|
<p>{x}</p>
|
||||||
|
{/each}
|
@ -0,0 +1,5 @@
|
|||||||
|
<script>
|
||||||
|
const { children } = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{@render children()}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { test } from '../../test';
|
||||||
|
import { flushSync } from 'svelte';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
async test({ assert, target }) {
|
||||||
|
assert.htmlEqual(target.innerHTML, 'test');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
import A from './A.svelte';
|
||||||
|
|
||||||
|
const B = $derived(A);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<B>
|
||||||
|
<B>test</B>
|
||||||
|
</B>
|
@ -0,0 +1,18 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
test({ assert, target }) {
|
||||||
|
const [change, increment] = target.querySelectorAll('button');
|
||||||
|
|
||||||
|
increment.click();
|
||||||
|
flushSync();
|
||||||
|
assert.htmlEqual(target.innerHTML, '<button>change handlers</button><button>1 / 1</button>');
|
||||||
|
|
||||||
|
change.click();
|
||||||
|
flushSync();
|
||||||
|
increment.click();
|
||||||
|
flushSync();
|
||||||
|
assert.htmlEqual(target.innerHTML, '<button>change handlers</button><button>3 / 3</button>');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,27 @@
|
|||||||
|
<script>
|
||||||
|
let delegated = $state(0);
|
||||||
|
let non_delegated = $state(0);
|
||||||
|
let attrs = $state({
|
||||||
|
onclick: () => {
|
||||||
|
delegated += 1;
|
||||||
|
},
|
||||||
|
onclickcapture: () => {
|
||||||
|
non_delegated += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onclick={() =>
|
||||||
|
(attrs = {
|
||||||
|
onclick: () => {
|
||||||
|
delegated += 2;
|
||||||
|
},
|
||||||
|
onclickcapture: () => {
|
||||||
|
non_delegated += 2;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
change handlers
|
||||||
|
</button>
|
||||||
|
<button {...attrs}>{delegated} / {non_delegated}</button>
|
@ -0,0 +1,9 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
compileOptions: {
|
||||||
|
dev: true
|
||||||
|
},
|
||||||
|
error: 'state_unsafe_mutation'
|
||||||
|
});
|
@ -0,0 +1,10 @@
|
|||||||
|
<script>
|
||||||
|
let a = $state(0);
|
||||||
|
|
||||||
|
let b = $state(0);
|
||||||
|
|
||||||
|
$inspect(a).with((...args)=>{
|
||||||
|
console.log(...args);
|
||||||
|
b++;
|
||||||
|
});
|
||||||
|
</script>
|
@ -0,0 +1,20 @@
|
|||||||
|
import { flushSync } from 'svelte';
|
||||||
|
import { test } from '../../test';
|
||||||
|
|
||||||
|
export default test({
|
||||||
|
compileOptions: {
|
||||||
|
dev: true
|
||||||
|
},
|
||||||
|
async test({ assert, target, logs }) {
|
||||||
|
const [a, b] = target.querySelectorAll('button');
|
||||||
|
assert.deepEqual(logs, ['init', 0]);
|
||||||
|
flushSync(() => {
|
||||||
|
b?.click();
|
||||||
|
});
|
||||||
|
assert.deepEqual(logs, ['init', 0]);
|
||||||
|
flushSync(() => {
|
||||||
|
a?.click();
|
||||||
|
});
|
||||||
|
assert.deepEqual(logs, ['init', 0, 'update', 1]);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,13 @@
|
|||||||
|
<script>
|
||||||
|
let a = $state(0);
|
||||||
|
|
||||||
|
let b = $state(0);
|
||||||
|
|
||||||
|
$inspect(a).with((...args)=>{
|
||||||
|
console.log(...args);
|
||||||
|
b;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={()=>a++}></button>
|
||||||
|
<button onclick={()=>b++}></button>
|
Loading…
Reference in new issue