Merge remote-tracking branch 'origin' into elliott/variadic-snippets

pull/10320/head
S. Elliott Johnson 2 years ago
commit 486370e4ee

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: allow transition undefined payload

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: apply key animations on proxied arrays

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: improve signal consumer tracking behavior

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: improve internal signal dependency checking logic

@ -20,6 +20,7 @@
"chilled-pumas-invite", "chilled-pumas-invite",
"chilly-dolphins-lick", "chilly-dolphins-lick",
"clean-eels-beg", "clean-eels-beg",
"clever-chefs-relate",
"clever-rockets-burn", "clever-rockets-burn",
"cold-birds-own", "cold-birds-own",
"cool-ants-leave", "cool-ants-leave",
@ -66,6 +67,7 @@
"itchy-beans-melt", "itchy-beans-melt",
"itchy-kings-deliver", "itchy-kings-deliver",
"itchy-lions-wash", "itchy-lions-wash",
"itchy-terms-guess",
"khaki-mails-draw", "khaki-mails-draw",
"khaki-moose-arrive", "khaki-moose-arrive",
"kind-deers-lay", "kind-deers-lay",
@ -98,6 +100,7 @@
"old-houses-drum", "old-houses-drum",
"old-mails-sneeze", "old-mails-sneeze",
"old-oranges-compete", "old-oranges-compete",
"olive-kangaroos-brake",
"orange-dingos-poke", "orange-dingos-poke",
"polite-dolphins-care", "polite-dolphins-care",
"polite-pumpkins-guess", "polite-pumpkins-guess",
@ -125,6 +128,7 @@
"shiny-baboons-play", "shiny-baboons-play",
"shiny-shrimps-march", "shiny-shrimps-march",
"slimy-clouds-talk", "slimy-clouds-talk",
"slimy-walls-draw",
"slow-chefs-dream", "slow-chefs-dream",
"small-papayas-laugh", "small-papayas-laugh",
"smart-parents-swim", "smart-parents-swim",
@ -134,6 +138,7 @@
"sour-forks-stare", "sour-forks-stare",
"sour-rules-march", "sour-rules-march",
"spicy-plums-admire", "spicy-plums-admire",
"stale-books-perform",
"stale-comics-look", "stale-comics-look",
"strong-gifts-smoke", "strong-gifts-smoke",
"strong-lemons-provide", "strong-lemons-provide",
@ -156,6 +161,7 @@
"thirty-impalas-repair", "thirty-impalas-repair",
"thirty-wombats-relax", "thirty-wombats-relax",
"three-suits-grin", "three-suits-grin",
"tidy-buses-whisper",
"tiny-kings-whisper", "tiny-kings-whisper",
"twelve-dragons-join", "twelve-dragons-join",
"twelve-onions-juggle", "twelve-onions-juggle",

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: correctly call exported state

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: take into account setters when spreading and binding

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: transform `{@render ...}` expression

@ -1,5 +1,21 @@
# svelte # svelte
## 5.0.0-next.30
### Patch Changes
- fix: allow transition undefined payload ([#10117](https://github.com/sveltejs/svelte/pull/10117))
- fix: apply key animations on proxied arrays ([#10113](https://github.com/sveltejs/svelte/pull/10113))
- fix: improve internal signal dependency checking logic ([#10111](https://github.com/sveltejs/svelte/pull/10111))
- fix: correctly call exported state ([#10114](https://github.com/sveltejs/svelte/pull/10114))
- fix: take into account setters when spreading and binding ([#10091](https://github.com/sveltejs/svelte/pull/10091))
- fix: transform `{@render ...}` expression ([#10116](https://github.com/sveltejs/svelte/pull/10116))
## 5.0.0-next.29 ## 5.0.0-next.29
### Patch Changes ### Patch Changes

@ -2,7 +2,7 @@
"name": "svelte", "name": "svelte",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"license": "MIT", "license": "MIT",
"version": "5.0.0-next.29", "version": "5.0.0-next.30",
"type": "module", "type": "module",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"engines": { "engines": {

@ -7,7 +7,7 @@ import { global_visitors } from './visitors/global.js';
import { javascript_visitors } from './visitors/javascript.js'; import { javascript_visitors } from './visitors/javascript.js';
import { javascript_visitors_runes } from './visitors/javascript-runes.js'; import { javascript_visitors_runes } from './visitors/javascript-runes.js';
import { javascript_visitors_legacy } from './visitors/javascript-legacy.js'; import { javascript_visitors_legacy } from './visitors/javascript-legacy.js';
import { serialize_get_binding } from './utils.js'; import { is_state_source, serialize_get_binding } from './utils.js';
import { remove_types } from '../typescript.js'; import { remove_types } from '../typescript.js';
/** /**
@ -242,9 +242,7 @@ export function client_component(source, analysis, options) {
const properties = analysis.exports.map(({ name, alias }) => { const properties = analysis.exports.map(({ name, alias }) => {
const binding = analysis.instance.scope.get(name); const binding = analysis.instance.scope.get(name);
const is_source = const is_source = binding !== null && is_state_source(binding, state);
(binding?.kind === 'state' || binding?.kind === 'frozen_state') &&
(!state.analysis.immutable || binding.reassigned);
// TODO This is always a getter because the `renamed-instance-exports` test wants it that way. // TODO This is always a getter because the `renamed-instance-exports` test wants it that way.
// Should we for code size reasons make it an init in runes mode and/or non-dev mode? // Should we for code size reasons make it an init in runes mode and/or non-dev mode?

@ -45,6 +45,18 @@ export function get_assignment_value(node, { state, visit }) {
} }
} }
/**
* @param {import('#compiler').Binding} binding
* @param {import('./types').ClientTransformState} state
* @returns {boolean}
*/
export function is_state_source(binding, state) {
return (
(binding.kind === 'state' || binding.kind === 'frozen_state') &&
(!state.analysis.immutable || binding.reassigned || state.analysis.accessors)
);
}
/** /**
* @param {import('estree').Identifier} node * @param {import('estree').Identifier} node
* @param {import('./types').ClientTransformState} state * @param {import('./types').ClientTransformState} state
@ -93,8 +105,7 @@ export function serialize_get_binding(node, state) {
} }
if ( if (
((binding.kind === 'state' || binding.kind === 'frozen_state') && is_state_source(binding, state) ||
(!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) ||
binding.kind === 'derived' || binding.kind === 'derived' ||
binding.kind === 'legacy_reactive' binding.kind === 'legacy_reactive'
) { ) {
@ -491,33 +502,6 @@ export function get_prop_source(binding, state, name, initial) {
return b.call('$.prop', ...args); return b.call('$.prop', ...args);
} }
/**
* Creates the output for a state declaration.
* @param {import('estree').VariableDeclarator} declarator
* @param {import('../../scope').Scope} scope
* @param {import('estree').Expression} value
*/
export function create_state_declarators(declarator, scope, value) {
// in the simple `let count = $state(0)` case, we rewrite `$state` as `$.source`
if (declarator.id.type === 'Identifier') {
return [b.declarator(declarator.id, b.call('$.mutable_source', value))];
}
const tmp = scope.generate('tmp');
const paths = extract_paths(declarator.id);
return [
b.declarator(b.id(tmp), value), // TODO inject declarator for opts, so we can use it below
...paths.map((path) => {
const value = path.expression?.(b.id(tmp));
const binding = scope.get(/** @type {import('estree').Identifier} */ (path.node).name);
return b.declarator(
path.node,
binding?.kind === 'state' ? b.call('$.mutable_source', value) : value
);
})
];
}
/** @param {import('estree').Expression} node */ /** @param {import('estree').Expression} node */
export function should_proxy_or_freeze(node) { export function should_proxy_or_freeze(node) {
if ( if (

@ -1,7 +1,33 @@
import { is_hoistable_function } from '../../utils.js'; import { is_hoistable_function } from '../../utils.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
import { extract_paths } from '../../../../utils/ast.js'; import { extract_paths } from '../../../../utils/ast.js';
import { create_state_declarators, get_prop_source, serialize_get_binding } from '../utils.js'; import { get_prop_source, serialize_get_binding } from '../utils.js';
/**
* Creates the output for a state declaration.
* @param {import('estree').VariableDeclarator} declarator
* @param {import('../../../scope.js').Scope} scope
* @param {import('estree').Expression} value
*/
function create_state_declarators(declarator, scope, value) {
if (declarator.id.type === 'Identifier') {
return [b.declarator(declarator.id, b.call('$.mutable_source', value))];
}
const tmp = scope.generate('tmp');
const paths = extract_paths(declarator.id);
return [
b.declarator(b.id(tmp), value),
...paths.map((path) => {
const value = path.expression?.(b.id(tmp));
const binding = scope.get(/** @type {import('estree').Identifier} */ (path.node).name);
return b.declarator(
path.node,
binding?.kind === 'state' ? b.call('$.mutable_source', value) : value
);
})
];
}
/** @type {import('../types.js').ComponentVisitors} */ /** @type {import('../types.js').ComponentVisitors} */
export const javascript_visitors_legacy = { export const javascript_visitors_legacy = {

@ -2,8 +2,8 @@ import { get_rune } from '../../../scope.js';
import { is_hoistable_function, transform_inspect_rune } from '../../utils.js'; import { is_hoistable_function, transform_inspect_rune } from '../../utils.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
import * as assert from '../../../../utils/assert.js'; import * as assert from '../../../../utils/assert.js';
import { create_state_declarators, get_prop_source, should_proxy_or_freeze } from '../utils.js'; import { get_prop_source, is_state_source, should_proxy_or_freeze } from '../utils.js';
import { unwrap_ts_expression } from '../../../../utils/ast.js'; import { extract_paths, unwrap_ts_expression } from '../../../../utils/ast.js';
/** @type {import('../types.js').ComponentVisitors} */ /** @type {import('../types.js').ComponentVisitors} */
export const javascript_visitors_runes = { export const javascript_visitors_runes = {
@ -223,66 +223,79 @@ export const javascript_visitors_runes = {
} }
const args = /** @type {import('estree').CallExpression} */ (init).arguments; const args = /** @type {import('estree').CallExpression} */ (init).arguments;
let value = const value =
args.length === 0 args.length === 0
? b.id('undefined') ? b.id('undefined')
: /** @type {import('estree').Expression} */ (visit(args[0])); : /** @type {import('estree').Expression} */ (visit(args[0]));
if (declarator.id.type === 'Identifier') { if (rune === '$state' || rune === '$state.frozen') {
if (rune === '$state') { /**
const binding = /** @type {import('#compiler').Binding} */ ( * @param {import('estree').Identifier} id
state.scope.get(declarator.id.name) * @param {import('estree').Expression} value
); */
const create_state_declarator = (id, value) => {
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name));
if (should_proxy_or_freeze(value)) { if (should_proxy_or_freeze(value)) {
value = b.call('$.proxy', value); value = b.call(rune === '$state' ? '$.proxy' : '$.freeze', value);
} }
if (is_state_source(binding, state)) {
if (!state.analysis.immutable || state.analysis.accessors || binding.reassigned) {
value = b.call('$.source', value); value = b.call('$.source', value);
} }
} else if (rune === '$state.frozen') { return value;
const binding = /** @type {import('#compiler').Binding} */ ( };
state.scope.get(declarator.id.name)
);
if (should_proxy_or_freeze(value)) {
value = b.call('$.freeze', value);
}
if (binding.reassigned) { if (declarator.id.type === 'Identifier') {
value = b.call('$.source', value); declarations.push(
} b.declarator(declarator.id, create_state_declarator(declarator.id, value))
);
} else { } else {
value = b.call('$.derived', b.thunk(value)); const tmp = state.scope.generate('tmp');
const paths = extract_paths(declarator.id);
declarations.push(
b.declarator(b.id(tmp), value),
...paths.map((path) => {
const value = path.expression?.(b.id(tmp));
const binding = state.scope.get(
/** @type {import('estree').Identifier} */ (path.node).name
);
return b.declarator(
path.node,
binding?.kind === 'state' || binding?.kind === 'frozen_state'
? create_state_declarator(binding.node, value)
: value
);
})
);
} }
declarations.push(b.declarator(declarator.id, value));
continue; continue;
} }
if (rune === '$derived') { if (rune === '$derived') {
const bindings = state.scope.get_bindings(declarator); if (declarator.id.type === 'Identifier') {
const id = state.scope.generate('derived_value'); declarations.push(b.declarator(declarator.id, b.call('$.derived', b.thunk(value))));
declarations.push( } else {
b.declarator( const bindings = state.scope.get_bindings(declarator);
b.id(id), const id = state.scope.generate('derived_value');
b.call( declarations.push(
'$.derived', b.declarator(
b.thunk( b.id(id),
b.block([ b.call(
b.let(declarator.id, value), '$.derived',
b.return(b.array(bindings.map((binding) => binding.node))) b.thunk(
]) b.block([
b.let(declarator.id, value),
b.return(b.array(bindings.map((binding) => binding.node)))
])
)
) )
) )
) );
); for (let i = 0; i < bindings.length; i++) {
for (let i = 0; i < bindings.length; i++) { bindings[i].expression = b.member(b.call('$.get', b.id(id)), b.literal(i), true);
bindings[i].expression = b.member(b.call('$.get', b.id(id)), b.literal(i), true); }
} }
continue; continue;
} }
declarations.push(...create_state_declarators(declarator, state.scope, value));
} }
if (declarations.length === 0) { if (declarations.length === 0) {

@ -1120,9 +1120,10 @@ const template_visitors = {
state.init.push(anchor); state.init.push(anchor);
state.template.push(t_expression(anchor_id)); state.template.push(t_expression(anchor_id));
const expression = /** @type {import('estree').Expression} */ (context.visit(node.expression));
const snippet_function = state.options.dev const snippet_function = state.options.dev
? b.call('$.validate_snippet', node.expression) ? b.call('$.validate_snippet', expression)
: node.expression; : expression;
const snippet_args = node.arguments.map((arg) => { const snippet_args = node.arguments.map((arg) => {
return /** @type {import('estree').Expression} */ (context.visit(arg)); return /** @type {import('estree').Expression} */ (context.visit(arg));

@ -417,7 +417,9 @@ function reconcile_tracked_array(
insert_each_item_block(block, dom, is_controlled, null); insert_each_item_block(block, dom, is_controlled, null);
} }
} else { } else {
var should_update_block = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_update_block =
(flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0 || is_animated;
var start = 0; var start = 0;
/** @type {null | Text | Element | Comment} */ /** @type {null | Text | Element | Comment} */
@ -500,7 +502,6 @@ function reconcile_tracked_array(
mark_lis(sources); mark_lis(sources);
} }
// If keys are animated, we need to do updates before actual moves // If keys are animated, we need to do updates before actual moves
var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_create; var should_create;
if (is_animated) { if (is_animated) {
var i = b_length; var i = b_length;

@ -2646,8 +2646,13 @@ const spread_props_handler = {
if (typeof p === 'object' && p !== null && key in p) return p[key]; if (typeof p === 'object' && p !== null && key in p) return p[key];
} }
}, },
getOwnPropertyDescriptor() { getOwnPropertyDescriptor(target, key) {
return { enumerable: true, configurable: true }; let i = target.props.length;
while (i--) {
let p = target.props[i];
if (is_function(p)) p = p();
if (typeof p === 'object' && p !== null && key in p) return get_descriptor(p, key);
}
}, },
has(target, key) { has(target, key) {
for (let p of target.props) { for (let p of target.props) {

@ -50,6 +50,8 @@ let current_queued_effects = [];
/** @type {Array<() => void>} */ /** @type {Array<() => void>} */
let current_queued_tasks = []; let current_queued_tasks = [];
/** @type {Array<() => void>} */
let current_queued_microtasks = [];
let flush_count = 0; let flush_count = 0;
// Handle signal reactivity tree dependencies and consumer // Handle signal reactivity tree dependencies and consumer
@ -349,15 +351,21 @@ function execute_signal_fn(signal) {
if (current_dependencies !== null) { if (current_dependencies !== null) {
let i; let i;
if (dependencies !== null) { if (dependencies !== null) {
const dep_length = dependencies.length; // Include any dependencies up until the current_dependencies_index.
const full_dependencies =
current_dependencies_index === 0
? dependencies
: dependencies.slice(0, current_dependencies_index).concat(current_dependencies);
const dep_length = full_dependencies.length;
// If we have more than 16 elements in the array then use a Set for faster performance // If we have more than 16 elements in the array then use a Set for faster performance
// TODO: evaluate if we should always just use a Set or not here? // TODO: evaluate if we should always just use a Set or not here?
const current_dependencies_set = dep_length > 16 ? new Set(current_dependencies) : null; const current_dependencies_set = dep_length > 16 ? new Set(full_dependencies) : null;
for (i = current_dependencies_index; i < dep_length; i++) { for (i = current_dependencies_index; i < dep_length; i++) {
const dependency = dependencies[i]; const dependency = full_dependencies[i];
if ( if (
(current_dependencies_set !== null && !current_dependencies_set.has(dependency)) || (current_dependencies_set !== null && !current_dependencies_set.has(dependency)) ||
!current_dependencies.includes(dependency) !full_dependencies.includes(dependency)
) { ) {
remove_consumer(signal, dependency, false); remove_consumer(signal, dependency, false);
} }
@ -448,10 +456,14 @@ function remove_consumer(signal, dependency, remove_unowned) {
function remove_consumers(signal, start_index, remove_unowned) { function remove_consumers(signal, start_index, remove_unowned) {
const dependencies = signal.d; const dependencies = signal.d;
if (dependencies !== null) { if (dependencies !== null) {
const active_dependencies = start_index === 0 ? null : dependencies.slice(0, start_index);
let i; let i;
for (i = start_index; i < dependencies.length; i++) { for (i = start_index; i < dependencies.length; i++) {
const dependency = dependencies[i]; const dependency = dependencies[i];
remove_consumer(signal, dependency, remove_unowned); // Avoid removing a consumer if we know that it is active (start_index will not be 0)
if (active_dependencies === null || !active_dependencies.includes(dependency)) {
remove_consumer(signal, dependency, remove_unowned);
}
} }
} }
} }
@ -573,6 +585,11 @@ function flush_queued_effects(effects) {
function process_microtask() { function process_microtask() {
is_micro_task_queued = false; is_micro_task_queued = false;
if (current_queued_microtasks.length > 0) {
const tasks = current_queued_microtasks.slice();
current_queued_microtasks = [];
run_all(tasks);
}
if (flush_count > 101) { if (flush_count > 101) {
return; return;
} }
@ -631,6 +648,18 @@ export function schedule_task(fn) {
current_queued_tasks.push(fn); current_queued_tasks.push(fn);
} }
/**
* @param {() => void} fn
* @returns {void}
*/
export function schedule_microtask(fn) {
if (!is_micro_task_queued) {
is_micro_task_queued = true;
queueMicrotask(process_microtask);
}
current_queued_microtasks.push(fn);
}
/** /**
* @returns {void} * @returns {void}
*/ */
@ -691,6 +720,9 @@ export function flushSync(fn) {
if (current_queued_pre_and_render_effects.length > 0 || effects.length > 0) { if (current_queued_pre_and_render_effects.length > 0 || effects.length > 0) {
flushSync(); flushSync();
} }
if (is_micro_task_queued) {
process_microtask();
}
if (is_task_queued) { if (is_task_queued) {
process_task(); process_task();
} }

@ -21,7 +21,7 @@ import {
managed_effect, managed_effect,
managed_pre_effect, managed_pre_effect,
mark_subtree_inert, mark_subtree_inert,
schedule_task, schedule_microtask,
untrack untrack
} from './runtime.js'; } from './runtime.js';
import { raf } from './timing.js'; import { raf } from './timing.js';
@ -279,6 +279,9 @@ function create_transition(dom, init, direction, effect) {
// @ts-ignore // @ts-ignore
payload = payload({ direction: curr_direction }); payload = payload({ direction: curr_direction });
} }
if (payload == null) {
return;
}
const duration = payload.duration ?? 300; const duration = payload.duration ?? 300;
const delay = payload.delay ?? 0; const delay = payload.delay ?? 0;
const css_fn = payload.css; const css_fn = payload.css;
@ -354,11 +357,15 @@ function create_transition(dom, init, direction, effect) {
cancelled = false; cancelled = false;
create_animation(); create_animation();
} }
dispatch_event(dom, 'introstart'); if (animation === null) {
if (needs_reverse) { transition.x();
/** @type {Animation | TickAnimation} */ (animation).reverse(); } else {
dispatch_event(dom, 'introstart');
if (needs_reverse) {
/** @type {Animation | TickAnimation} */ (animation).reverse();
}
/** @type {Animation | TickAnimation} */ (animation).play();
} }
/** @type {Animation | TickAnimation} */ (animation).play();
}, },
// out // out
o() { o() {
@ -368,11 +375,15 @@ function create_transition(dom, init, direction, effect) {
cancelled = false; cancelled = false;
create_animation(); create_animation();
} }
dispatch_event(dom, 'outrostart'); if (animation === null) {
if (needs_reverse) { transition.x();
/** @type {Animation | TickAnimation} */ (animation).reverse();
} else { } else {
/** @type {Animation | TickAnimation} */ (animation).play(); dispatch_event(dom, 'outrostart');
if (needs_reverse) {
/** @type {Animation | TickAnimation} */ (animation).reverse();
} else {
/** @type {Animation | TickAnimation} */ (animation).play();
}
} }
}, },
// cancel // cancel
@ -671,7 +682,7 @@ function each_item_animate(block, transitions, index, index_is_reactive) {
transition.c(); transition.c();
} }
} }
schedule_task(() => { schedule_microtask(() => {
trigger_transitions(transitions, 'key', from); trigger_transitions(transitions, 'key', from);
}); });
} }

@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version * https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string} * @type {string}
*/ */
export const VERSION = '5.0.0-next.29'; export const VERSION = '5.0.0-next.30';
export const PUBLIC_VERSION = '5'; export const PUBLIC_VERSION = '5';

@ -1,13 +1,11 @@
import { test } from '../../test'; import { test } from '../../test';
export default test({ export default test({
html: `<button>0</button>`, html: `<button>0 / 0</button>`,
async test({ assert, target, window }) { async test({ assert, target }) {
const btn = target.querySelector('button'); const btn = target.querySelector('button');
const clickEvent = new window.Event('click', { bubbles: true }); await btn?.click();
await btn?.dispatchEvent(clickEvent); assert.htmlEqual(target.innerHTML, `<button>1 / 1</button>`);
assert.htmlEqual(target.innerHTML, `<button>1</button>`);
} }
}); });

@ -2,6 +2,7 @@
import { setup } from './utils.js'; import { setup } from './utils.js';
let { num } = $state(setup()); let { num } = $state(setup());
let { num: num_frozen } = $state(setup());
</script> </script>
<button on:click={() => num++}>{num}</button> <button on:click={() => { num++; num_frozen++; }}>{num} / {num_frozen}</button>

@ -0,0 +1,21 @@
import { test } from '../../test';
export default test({
html: `<button class="foo">0</button><button class="foo">0</button>`,
async test({ assert, target }) {
const [btn1, btn2] = target.querySelectorAll('button');
await btn1?.click();
assert.htmlEqual(
target.innerHTML,
`<button class="foo">1</button><button class="foo">1</button>`
);
await btn2?.click();
assert.htmlEqual(
target.innerHTML,
`<button class="foo">2</button><button class="foo">2</button>`
);
}
});

@ -0,0 +1,6 @@
<script>
let { value, ...props } = $props();
</script>
<button {...props} onclick={() => value++}>{value}</button>

@ -0,0 +1,12 @@
<script>
import Button from './button.svelte';
let value = $state(0);
const props = {
class: 'foo'
};
</script>
<Button {...props} bind:value />
<button {...props} onclick={() => value++}>{value}</button>

@ -0,0 +1,40 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `<button>Add Item</button>`,
async test({ assert, target }) {
const [btn1] = target.querySelectorAll('button');
flushSync(() => {
btn1.click();
btn1.click();
});
assert.htmlEqual(
target.innerHTML,
`<button>Add Item</button><button>Index 0\n|\nItem 0</button><button>Index 1\n|\nItem 1</button>`
);
let [btn2, btn3, btn4] = target.querySelectorAll('button');
flushSync(() => {
btn4.click();
btn3.click();
});
assert.htmlEqual(target.innerHTML, `<button>Add Item</button>`);
let [btn5] = target.querySelectorAll('button');
flushSync(() => {
btn5.click();
});
assert.htmlEqual(
target.innerHTML,
`<button>Add Item</button><button>Index 0\n|\nItem 2</button>`
);
}
});

@ -0,0 +1,18 @@
<script>
let arr = $state([]);
let counter = 0
function addItem() {
arr.push(`${counter++}`)
}
function removeItem(i) {
arr.splice(i, 1)
}
</script>
<button onclick={addItem}>Add Item</button>
{#each arr as item, i}
<button onclick={() => removeItem(i)}>Index {i} | Item {item}</button>
{/each}

@ -33,7 +33,7 @@ export default test({
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
`<p>test costs $1</p><p>test 2 costs $2000</p><p>test costs $1</p><p>test 2 costs $2000</p><button>add</button><button>change</button><button>reload</button>` `<p>test costs $1</p><p>test 2 costs $2</p><p>test costs $1</p><p>test 2 costs $2</p><button>add</button><button>change</button><button>reload</button>`
); );
} }
}); });

@ -0,0 +1,12 @@
import { test } from '../../test';
export default test({
async test({ assert, target }) {
assert.htmlEqual(target.innerHTML, `0 0 <button>0 / 0</button>`);
const [btn] = target.querySelectorAll('button');
btn?.click();
await Promise.resolve();
assert.htmlEqual(target.innerHTML, '0 1 <button>0 / 1</button>');
}
});

@ -0,0 +1,7 @@
<script>
import Sub from './sub.svelte'
let sub = $state();
</script>
<Sub bind:this={sub} />
<button on:click={() => sub.increment()}>{sub?.count1.value} / {sub?.count2.value}</button>

@ -0,0 +1,15 @@
<script>
export const count1 = $state.frozen({value: 0});
export const count2 = $state({value: 0});
export function increment() {
count2.value += 1;
}
</script>
{count1.value}
{count2.value}
<!-- so that count1/2 become sources -->
<svelte:options accessors />

@ -0,0 +1,40 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `<button>show</button><button>animate</button>`,
async test({ assert, target }) {
const [btn1, btn2] = target.querySelectorAll('button');
flushSync(() => {
btn1.click();
});
assert.htmlEqual(
target.innerHTML,
`<button>show</button><button>animate</button><h1>Hello\n!</h1>`
);
flushSync(() => {
btn1.click();
});
assert.htmlEqual(target.innerHTML, `<button>show</button><button>animate</button>`);
flushSync(() => {
btn2.click();
});
assert.htmlEqual(target.innerHTML, `<button>show</button><button>animate</button>`);
flushSync(() => {
btn1.click();
});
assert.htmlEqual(
target.innerHTML,
`<button>show</button><button>animate</button><h1 style="opacity: 0;">Hello\n!</h1>`
);
}
});

@ -0,0 +1,16 @@
<script>
import { fade } from 'svelte/transition';
let show = $state(false);
let animate = $state(false);
function maybe(node, animate) {
if (animate) return fade(node);
}
</script>
<button onclick={() => show = !show}>show</button><button onclick={() => animate = !animate}>animate</button>
{#if show}
<h1 transition:maybe={animate}>Hello {name}!</h1>
{/if}

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
html: `<p>hello world</p>`
});

@ -0,0 +1,11 @@
<script>
import { writable } from 'svelte/store';
let snippet = writable(hello);
</script>
{#snippet hello()}
<p>hello world</p>
{/snippet}
{@render $snippet()}

@ -0,0 +1,22 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `<p>0 - 0</p><button>+</button`,
async test({ assert, target }) {
const [btn1] = target.querySelectorAll('button');
flushSync(() => {
btn1.click();
});
assert.htmlEqual(target.innerHTML, `<p>1 - 1</p><button>+</button`);
flushSync(() => {
btn1.click();
});
assert.htmlEqual(target.innerHTML, `<p>2 - 2</p><button>+</button`);
}
});

@ -0,0 +1,12 @@
<script>
let x = $state({a: 0, b:0});
let count = 0;
</script>
<p>{x.a} - {x.b}</p>
<button onclick={() => {
const a = ++count;
x = {a, b: a};
}}>+</button>
Loading…
Cancel
Save