chore: rethink props ()

Cleaned up prop_source and renamed it to prop. Updated tests accordingly
pull/9835/head
Rich Harris 1 year ago committed by GitHub
parent c9c2bde5e7
commit b20b4617c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: refactor props handling

@ -255,8 +255,8 @@ export function client_component(source, analysis, options) {
const key = binding.prop_alias ?? name; const key = binding.prop_alias ?? name;
properties.push( properties.push(
b.get(key, [b.return(b.call('$.get', b.id(name)))]), b.get(key, [b.return(b.call(b.id(name)))]),
b.set(key, [b.stmt(b.call('$.set_sync', b.id(name), b.id('$$value')))]) b.set(key, [b.stmt(b.call(b.id(name), b.id('$$value'))), b.stmt(b.call('$.flushSync'))])
); );
} }
} }

@ -2,9 +2,10 @@ import * as b from '../../../utils/builders.js';
import { extract_paths, is_simple_expression } from '../../../utils/ast.js'; import { extract_paths, is_simple_expression } from '../../../utils/ast.js';
import { error } from '../../../errors.js'; import { error } from '../../../errors.js';
import { import {
PROPS_CALL_DEFAULT_VALUE, PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE, PROPS_IS_IMMUTABLE,
PROPS_IS_RUNES PROPS_IS_RUNES,
PROPS_IS_UPDATED
} from '../../../../constants.js'; } from '../../../../constants.js';
/** /**
@ -73,12 +74,14 @@ export function serialize_get_binding(node, state) {
} }
if ( if (
!state.analysis.accessors && state.analysis.accessors ||
!(state.analysis.immutable ? binding.reassigned : binding.mutated) && (state.analysis.immutable ? binding.reassigned : binding.mutated) ||
!binding.initial binding.initial
) { ) {
return b.member(b.id('$$props'), node); return b.call(node);
} }
return b.member(b.id('$$props'), node);
} }
if (binding.kind === 'legacy_reactive_import') { if (binding.kind === 'legacy_reactive_import') {
@ -89,7 +92,6 @@ export function serialize_get_binding(node, state) {
(binding.kind === 'state' && (binding.kind === 'state' &&
(!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) || (!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) ||
binding.kind === 'derived' || binding.kind === 'derived' ||
binding.kind === 'prop' ||
binding.kind === 'legacy_reactive' binding.kind === 'legacy_reactive'
) { ) {
return b.call('$.get', node); return b.call('$.get', node);
@ -208,9 +210,12 @@ export function serialize_set_binding(node, context, fallback) {
} }
const value = get_assignment_value(node, context); const value = get_assignment_value(node, context);
const serialize = () => { const serialize = () => {
if (left === node.left) { if (left === node.left) {
if (is_store) { if (binding.kind === 'prop') {
return b.call(left, value);
} else if (is_store) {
return b.call('$.store_set', serialize_get_binding(b.id(left_name), state), value); return b.call('$.store_set', serialize_get_binding(b.id(left_name), state), value);
} else { } else {
return b.call( return b.call(
@ -232,15 +237,27 @@ export function serialize_set_binding(node, context, fallback) {
b.call('$' + left_name) b.call('$' + left_name)
); );
} else if (!state.analysis.runes) { } else if (!state.analysis.runes) {
return b.call( if (binding.kind === 'prop') {
'$.mutate', return b.call(
b.id(left_name), left,
b.assignment( b.assignment(
node.operator, node.operator,
/** @type {import('estree').Pattern} */ (visit(node.left)), /** @type {import('estree').Pattern} */ (visit(node.left)),
value value
) ),
); b.literal(true)
);
} else {
return b.call(
'$.mutate',
b.id(left_name),
b.assignment(
node.operator,
/** @type {import('estree').Pattern} */ (visit(node.left)),
value
)
);
}
} else { } else {
return b.assignment( return b.assignment(
node.operator, node.operator,
@ -345,12 +362,13 @@ export function serialize_hoistable_params(node, context) {
} }
/** /**
* @param {import('#compiler').Binding} binding
* @param {import('./types').ComponentClientTransformState} state * @param {import('./types').ComponentClientTransformState} state
* @param {string} name * @param {string} name
* @param {import('estree').Expression | null} [initial] * @param {import('estree').Expression | null} [initial]
* @returns * @returns
*/ */
export function get_prop_source(state, name, initial) { export function get_prop_source(binding, state, name, initial) {
/** @type {import('estree').Expression[]} */ /** @type {import('estree').Expression[]} */
const args = [b.id('$$props'), b.literal(name)]; const args = [b.id('$$props'), b.literal(name)];
@ -364,6 +382,13 @@ export function get_prop_source(state, name, initial) {
flags |= PROPS_IS_RUNES; flags |= PROPS_IS_RUNES;
} }
if (
state.analysis.accessors ||
(state.analysis.immutable ? binding.reassigned : binding.mutated)
) {
flags |= PROPS_IS_UPDATED;
}
/** @type {import('estree').Expression | undefined} */ /** @type {import('estree').Expression | undefined} */
let arg; let arg;
@ -382,7 +407,7 @@ export function get_prop_source(state, name, initial) {
arg = b.thunk(initial); arg = b.thunk(initial);
} }
flags |= PROPS_CALL_DEFAULT_VALUE; flags |= PROPS_IS_LAZY_INITIAL;
} }
} }
@ -391,7 +416,7 @@ export function get_prop_source(state, name, initial) {
if (arg) args.push(arg); if (arg) args.push(arg);
} }
return b.call('$.prop_source', ...args); return b.call('$.prop', ...args);
} }
/** /**

@ -45,6 +45,7 @@ export const global_visitors = {
const binding = state.scope.get(argument.name); const binding = state.scope.get(argument.name);
const is_store = binding?.kind === 'store_sub'; const is_store = binding?.kind === 'store_sub';
const name = is_store ? argument.name.slice(1) : argument.name; const name = is_store ? argument.name.slice(1) : argument.name;
// use runtime functions for smaller output // use runtime functions for smaller output
if ( if (
binding?.kind === 'state' || binding?.kind === 'state' ||
@ -53,18 +54,28 @@ export const global_visitors = {
binding?.kind === 'prop' || binding?.kind === 'prop' ||
is_store is_store
) { ) {
let fn = node.operator === '++' ? '$.increment' : '$.decrement'; /** @type {import('estree').Expression[]} */
const args = [];
let fn = '$.update';
if (node.prefix) fn += '_pre'; if (node.prefix) fn += '_pre';
if (is_store) { if (is_store) {
fn += '_store'; fn += '_store';
return b.call(fn, serialize_get_binding(b.id(name), state), b.call('$' + name)); args.push(serialize_get_binding(b.id(name), state), b.call('$' + name));
} else { } else {
return b.call(fn, b.id(name)); if (binding.kind === 'prop') fn += '_prop';
args.push(b.id(name));
} }
} else {
return next(); if (node.operator === '--') {
args.push(b.literal(-1));
}
return b.call(fn, ...args);
} }
return next();
} else if ( } else if (
argument.type === 'MemberExpression' && argument.type === 'MemberExpression' &&
argument.object.type === 'ThisExpression' && argument.object.type === 'ThisExpression' &&

@ -55,7 +55,7 @@ export const javascript_visitors_legacy = {
b.declarator( b.declarator(
path.node, path.node,
binding.kind === 'prop' binding.kind === 'prop'
? get_prop_source(state, binding.prop_alias ?? name, value) ? get_prop_source(binding, state, binding.prop_alias ?? name, value)
: value : value
) )
); );
@ -76,6 +76,7 @@ export const javascript_visitors_legacy = {
b.declarator( b.declarator(
declarator.id, declarator.id,
get_prop_source( get_prop_source(
binding,
state, state,
binding.prop_alias ?? declarator.id.name, binding.prop_alias ?? declarator.id.name,
declarator.init && declarator.init &&
@ -107,8 +108,11 @@ export const javascript_visitors_legacy = {
}; };
}, },
LabeledStatement(node, context) { LabeledStatement(node, context) {
if (context.path.length > 1) return; if (context.path.length > 1 || node.label.name !== '$') {
if (node.label.name !== '$') return; context.next();
return;
}
const state = context.state; const state = context.state;
// To recreate Svelte 4 behaviour, we track the dependencies // To recreate Svelte 4 behaviour, we track the dependencies
// the compiler can 'see', but we untrack the effect itself // the compiler can 'see', but we untrack the effect itself

@ -185,7 +185,7 @@ export const javascript_visitors_runes = {
const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name)); const binding = /** @type {import('#compiler').Binding} */ (state.scope.get(id.name));
if (binding.reassigned || state.analysis.accessors || initial) { if (binding.reassigned || state.analysis.accessors || initial) {
declarations.push(b.declarator(id, get_prop_source(state, name, initial))); declarations.push(b.declarator(id, get_prop_source(binding, state, name, initial)));
} }
} else { } else {
// RestElement // RestElement

@ -7,7 +7,8 @@ export const EACH_IS_IMMUTABLE = 1 << 6;
export const PROPS_IS_IMMUTABLE = 1; export const PROPS_IS_IMMUTABLE = 1;
export const PROPS_IS_RUNES = 1 << 1; export const PROPS_IS_RUNES = 1 << 1;
export const PROPS_CALL_DEFAULT_VALUE = 1 << 2; export const PROPS_IS_UPDATED = 1 << 2;
export const PROPS_IS_LAZY_INITIAL = 1 << 3;
/** List of Element events that will be delegated */ /** List of Element events that will be delegated */
export const DelegatedEvents = [ export const DelegatedEvents = [

@ -3,7 +3,7 @@ import {
effect_active, effect_active,
get, get,
set, set,
increment, update,
source, source,
updating_derived, updating_derived,
UNINITIALIZED, UNINITIALIZED,
@ -143,7 +143,7 @@ const handler = {
const s = metadata.s.get(prop); const s = metadata.s.get(prop);
if (s !== undefined) set(s, UNINITIALIZED); if (s !== undefined) set(s, UNINITIALIZED);
if (prop in target) increment(metadata.v); if (prop in target) update(metadata.v);
return delete target[prop]; return delete target[prop];
}, },
@ -224,7 +224,7 @@ const handler = {
} }
} }
if (not_has) { if (not_has) {
increment(metadata.v); update(metadata.v);
} }
// @ts-ignore // @ts-ignore
target[prop] = value; target[prop] = value;

@ -2658,7 +2658,7 @@ export function createRoot(component, options) {
/** @param {any} value */ /** @param {any} value */
set(value) { set(value) {
// @ts-expect-error TS doesn't know key exists on accessor // @ts-expect-error TS doesn't know key exists on accessor
accessors[key] = value; flushSync(() => (accessors[key] = value));
}, },
enumerable: true enumerable: true
}); });

@ -2,16 +2,20 @@ import { DEV } from 'esm-env';
import { subscribe_to_store } from '../../store/utils.js'; import { subscribe_to_store } from '../../store/utils.js';
import { EMPTY_FUNC, run_all } from '../common.js'; import { EMPTY_FUNC, run_all } from '../common.js';
import { get_descriptor, get_descriptors, is_array } from './utils.js'; import { get_descriptor, get_descriptors, is_array } from './utils.js';
import { PROPS_CALL_DEFAULT_VALUE, PROPS_IS_IMMUTABLE, PROPS_IS_RUNES } from '../../constants.js'; import {
PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE,
PROPS_IS_RUNES,
PROPS_IS_UPDATED
} from '../../constants.js';
import { readonly } from './proxy/readonly.js'; import { readonly } from './proxy/readonly.js';
import { observe, proxy } from './proxy/proxy.js'; import { proxy } from './proxy/proxy.js';
export const SOURCE = 1; export const SOURCE = 1;
export const DERIVED = 1 << 1; export const DERIVED = 1 << 1;
export const EFFECT = 1 << 2; export const EFFECT = 1 << 2;
export const PRE_EFFECT = 1 << 3; export const PRE_EFFECT = 1 << 3;
export const RENDER_EFFECT = 1 << 4; export const RENDER_EFFECT = 1 << 4;
export const SYNC_EFFECT = 1 << 5;
const MANAGED = 1 << 6; const MANAGED = 1 << 6;
const UNOWNED = 1 << 7; const UNOWNED = 1 << 7;
export const CLEAN = 1 << 8; export const CLEAN = 1 << 8;
@ -20,7 +24,7 @@ export const MAYBE_DIRTY = 1 << 10;
export const INERT = 1 << 11; export const INERT = 1 << 11;
export const DESTROYED = 1 << 12; export const DESTROYED = 1 << 12;
const IS_EFFECT = EFFECT | PRE_EFFECT | RENDER_EFFECT | SYNC_EFFECT; const IS_EFFECT = EFFECT | PRE_EFFECT | RENDER_EFFECT;
const FLUSH_MICROTASK = 0; const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1; const FLUSH_SYNC = 1;
@ -536,7 +540,7 @@ function process_microtask() {
*/ */
export function schedule_effect(signal, sync) { export function schedule_effect(signal, sync) {
const flags = signal.f; const flags = signal.f;
if (sync || (flags & SYNC_EFFECT) !== 0) { if (sync) {
execute_effect(signal); execute_effect(signal);
set_signal_status(signal, CLEAN); set_signal_status(signal, CLEAN);
} else { } else {
@ -1284,14 +1288,6 @@ export function invalidate_effect(init) {
return internal_create_effect(PRE_EFFECT, init, true, current_block, true); return internal_create_effect(PRE_EFFECT, init, true, current_block, true);
} }
/**
* @param {() => void | (() => void)} init
* @returns {import('./types.js').EffectSignal}
*/
function sync_effect(init) {
return internal_create_effect(SYNC_EFFECT, init, true, current_block, true);
}
/** /**
* @template {import('./types.js').Block} B * @template {import('./types.js').Block} B
* @param {(block: B) => void | (() => void)} init * @param {(block: B) => void | (() => void)} init
@ -1390,124 +1386,114 @@ export function is_store(val) {
/** /**
* This function is responsible for synchronizing a possibly bound prop with the inner component state. * This function is responsible for synchronizing a possibly bound prop with the inner component state.
* It is used whenever the compiler sees that the component writes to the prop. * It is used whenever the compiler sees that the component writes to the prop, or when it has a default prop_value.
*
* - If the parent passes down a prop without binding, like `<Component prop={value} />`, then create a signal
* that updates whenever the value is updated from the parent or from within the component itself
* - If the parent passes down a prop with a binding, like `<Component bind:prop={value} />`, then
* - if the thing that is passed along is the original signal (not a property on it), and the equality functions
* are equal, then just use that signal, no need to create an intermediate one
* - otherwise create a signal that updates whenever the value is updated from the parent, and when it's updated
* from within the component itself, call the setter of the parent which will propagate the value change back
* @template V * @template V
* @param {Record<string, unknown>} props * @param {Record<string, unknown>} props
* @param {string} key * @param {string} key
* @param {number} flags * @param {number} flags
* @param {V | (() => V)} [default_value] * @param {V | (() => V)} [initial]
* @returns {import('./types.js').Signal<V> | (() => V)} * @returns {(() => V | ((arg: V) => V) | ((arg: V, mutation: boolean) => V))}
*/ */
export function prop_source(props, key, flags, default_value) { export function prop(props, key, flags, initial) {
const call_default_value = (flags & PROPS_CALL_DEFAULT_VALUE) !== 0; var immutable = (flags & PROPS_IS_IMMUTABLE) !== 0;
const immutable = (flags & PROPS_IS_IMMUTABLE) !== 0; var runes = (flags & PROPS_IS_RUNES) !== 0;
const runes = (flags & PROPS_IS_RUNES) !== 0;
const update_bound_prop = get_descriptor(props, key)?.set;
let value = props[key];
const should_set_default_value = value === undefined && default_value !== undefined;
if (update_bound_prop && runes && default_value !== undefined) { var setter = get_descriptor(props, key)?.set;
if (DEV && setter && runes && initial !== undefined) {
// TODO consolidate all these random runtime errors // TODO consolidate all these random runtime errors
throw new Error('Cannot use fallback values with bind:'); throw new Error('Cannot use fallback values with bind:');
} }
if (should_set_default_value) { var prop_value = /** @type {V} */ (props[key]);
value =
// @ts-expect-error would need a cumbersome method overload to type this if (prop_value === undefined && initial !== undefined) {
call_default_value ? default_value() : default_value; // @ts-expect-error would need a cumbersome method overload to type this
if ((flags & PROPS_IS_LAZY_INITIAL) !== 0) initial = initial();
if (DEV && runes) { if (DEV && runes) {
value = readonly(proxy(/** @type {any} */ (value))); initial = readonly(proxy(/** @type {any} */ (initial)));
} }
prop_value = /** @type {V} */ (initial);
if (setter) setter(prop_value);
} }
const source_signal = immutable ? source(value) : mutable_source(value); var getter = () => {
var value = /** @type {V} */ (props[key]);
if (value !== undefined) initial = undefined;
return value === undefined ? /** @type {V} */ (initial) : value;
};
// Synchronize prop changes with source signal. // easy mode — prop is never written to
// Needs special equality checking because the prop in the if ((flags & PROPS_IS_UPDATED) === 0) {
// parent could be changed through `foo.bar = 'new value'`. return getter;
let ignore_next1 = false; }
let ignore_next2 = false;
let did_update_to_defined = !should_set_default_value;
let mount = true; // intermediate mode — prop is written to, but the parent component had
sync_effect(() => { // `bind:foo` which means we can just call `$$props.foo = value` directly
observe(props); if (setter) {
return function (/** @type {V} */ value) {
if (arguments.length === 1) {
/** @type {Function} */ (setter)(value);
return value;
} else {
return getter();
}
};
}
// Before if to ensure signal dependency is registered // hard mode. this is where it gets ugly — the value in the child should
const propagating_value = props[key]; // synchronize with the parent, but it should also be possible to temporarily
if (mount) { // set the value to something else locally.
mount = false; var from_child = false;
return; var was_from_child = false;
}
if (ignore_next1) { // The derived returns the current value. The underlying mutable
ignore_next1 = false; // source is written to from various places to persist this value.
return; var inner_current_value = mutable_source(prop_value);
var current_value = derived(() => {
var parent_value = getter();
var child_value = get(inner_current_value);
if (from_child) {
from_child = false;
was_from_child = true;
return child_value;
} }
if ( was_from_child = false;
// Ensure that updates from undefined to undefined are ignored return (inner_current_value.v = parent_value);
(did_update_to_defined || propagating_value !== undefined) &&
not_equal(immutable, propagating_value, source_signal.v)
) {
ignore_next2 = true;
did_update_to_defined = true;
// TODO figure out why we need it this way and the explain in a comment;
// some tests fail is we just do set_signal_value(source_signal, propagating_value)
untrack(() => set_signal_value(source_signal, propagating_value));
}
}); });
if (update_bound_prop !== undefined) { if (!immutable) current_value.e = safe_equal;
let ignore_first = !should_set_default_value;
sync_effect(() => {
// Before if to ensure signal dependency is registered
const propagating_value = get(source_signal);
if (ignore_first) {
ignore_first = false;
return;
}
if (ignore_next2) {
ignore_next2 = false;
return;
}
ignore_next1 = true; return function (/** @type {V} */ value, mutation = false) {
did_update_to_defined = true; var current = get(current_value);
untrack(() => update_bound_prop(propagating_value));
});
}
return /** @type {import('./types.js').Signal<V>} */ (source_signal); // legacy nonsense — need to ensure the source is invalidated when necessary
} if (is_signals_recorded) {
// set this so that we don't reset to the parent value if `d`
// is invalidated because of `invalidate_inner_signals` (rather
// than because the parent or child value changed)
from_child = was_from_child;
// invoke getters so that signals are picked up by `invalidate_inner_signals`
getter();
get(inner_current_value);
}
/** if (arguments.length > 0) {
* @param {boolean} immutable if (mutation || (immutable ? value !== current : safe_not_equal(value, current))) {
* @param {unknown} a from_child = true;
* @param {unknown} b set(inner_current_value, mutation ? current : value);
* @returns {boolean} get(current_value); // force a synchronisation immediately
*/ }
function not_equal(immutable, a, b) {
return immutable ? immutable_not_equal(a, b) : safe_not_equal(a, b);
}
/** return value;
* @param {unknown} a }
* @param {unknown} b
* @returns {boolean} return current;
*/ };
function immutable_not_equal(a, b) {
// eslint-disable-next-line eqeqeq
return a != a ? b == b : a !== b;
} }
/** /**
@ -1584,82 +1570,67 @@ export function bubble_event($$props, event) {
/** /**
* @param {import('./types.js').Signal<number>} signal * @param {import('./types.js').Signal<number>} signal
* @param {1 | -1} [d]
* @returns {number} * @returns {number}
*/ */
export function increment(signal) { export function update(signal, d = 1) {
const value = get(signal); const value = get(signal);
set_signal_value(signal, value + 1); set_signal_value(signal, value + d);
return value; return value;
} }
/** /**
* @param {import('./types.js').Store<number>} store * @param {((value?: number) => number)} fn
* @param {number} store_value * @param {1 | -1} [d]
* @returns {number}
*/
export function increment_store(store, store_value) {
store.set(store_value + 1);
return store_value;
}
/**
* @param {import('./types.js').Signal<number>} signal
* @returns {number} * @returns {number}
*/ */
export function decrement(signal) { export function update_prop(fn, d = 1) {
const value = get(signal); const value = fn();
set_signal_value(signal, value - 1); fn(value + d);
return value; return value;
} }
/** /**
* @param {import('./types.js').Store<number>} store * @param {import('./types.js').Store<number>} store
* @param {number} store_value * @param {number} store_value
* @param {1 | -1} [d]
* @returns {number} * @returns {number}
*/ */
export function decrement_store(store, store_value) { export function update_store(store, store_value, d = 1) {
store.set(store_value - 1); store.set(store_value + d);
return store_value; return store_value;
} }
/** /**
* @param {import('./types.js').Signal<number>} signal * @param {import('./types.js').Signal<number>} signal
* @param {1 | -1} [d]
* @returns {number} * @returns {number}
*/ */
export function increment_pre(signal) { export function update_pre(signal, d = 1) {
const value = get(signal) + 1; const value = get(signal) + d;
set_signal_value(signal, value); set_signal_value(signal, value);
return value; return value;
} }
/** /**
* @param {import('./types.js').Store<number>} store * @param {((value?: number) => number)} fn
* @param {number} store_value * @param {1 | -1} [d]
* @returns {number}
*/
export function increment_pre_store(store, store_value) {
const value = store_value + 1;
store.set(value);
return value;
}
/**
* @param {import('./types.js').Signal<number>} signal
* @returns {number} * @returns {number}
*/ */
export function decrement_pre(signal) { export function update_pre_prop(fn, d = 1) {
const value = get(signal) - 1; const value = fn() + d;
set_signal_value(signal, value); fn(value);
return value; return value;
} }
/** /**
* @param {import('./types.js').Store<number>} store * @param {import('./types.js').Store<number>} store
* @param {number} store_value * @param {number} store_value
* @param {1 | -1} [d]
* @returns {number} * @returns {number}
*/ */
export function decrement_pre_store(store, store_value) { export function update_pre_store(store, store_value, d = 1) {
const value = store_value - 1; const value = store_value + d;
store.set(value); store.set(value);
return value; return value;
} }

@ -7,7 +7,7 @@ export {
source, source,
mutable_source, mutable_source,
derived, derived,
prop_source, prop,
user_effect, user_effect,
render_effect, render_effect,
pre_effect, pre_effect,
@ -17,14 +17,12 @@ export {
safe_equal, safe_equal,
tick, tick,
untrack, untrack,
increment, update,
increment_store, update_prop,
decrement, update_store,
decrement_store, update_pre,
increment_pre, update_pre_prop,
increment_pre_store, update_pre_store,
decrement_pre,
decrement_pre_store,
mutate, mutate,
mutate_store, mutate_store,
value_or_fallback, value_or_fallback,

@ -16,5 +16,5 @@
export let updates = []; export let updates = [];
$: updates = [...updates, value]; $: updates = [...updates, value];
</script> </script>
<div>child: {value?.foo} | updates: {updates.length}</div> <div>child: {value?.foo} | updates: {updates.length}</div>

@ -34,7 +34,7 @@ export default test({
p = parents['reactive_mutate']; p = parents['reactive_mutate'];
assert.deepEqual(p.value, { foo: 'kid' }); assert.deepEqual(p.value, { foo: 'kid' });
assert.equal(p.updates.length, 2); assert.equal(p.updates.length, 1);
p = parents['init_update']; p = parents['init_update'];
assert.deepEqual(p.value, { foo: 'kid' }); assert.deepEqual(p.value, { foo: 'kid' });
@ -42,6 +42,6 @@ export default test({
p = parents['init_mutate']; p = parents['init_mutate'];
assert.deepEqual(p.value, { foo: 'kid' }); assert.deepEqual(p.value, { foo: 'kid' });
assert.equal(p.updates.length, 2); assert.equal(p.updates.length, 1);
} }
}); });

@ -80,7 +80,7 @@ export default test({
select.dispatchEvent(change); select.dispatchEvent(change);
}); });
assert.equal(component.selected, tasks[1]); assert.deepEqual(component.selected, tasks[1]); // TODO this should be assert.equal, but that crashes the entire test suite in mysterious ways... something to do with proxies not being reused?
assert.ok(!input.checked); assert.ok(!input.checked);
input.checked = true; input.checked = true;

@ -67,7 +67,7 @@ export default test({
` `
); );
component.selected = [values[1], values[2]]; component.selected = [component.values[1], component.values[2]];
assert.equal(inputs[0].checked, false); assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, true); assert.equal(inputs[1].checked, true);
assert.equal(inputs[2].checked, true); assert.equal(inputs[2].checked, true);

@ -67,7 +67,7 @@ export default test({
` `
); );
component.selected = [values[1], values[2]]; component.selected = [component.values[1], component.values[2]];
assert.equal(inputs[0].checked, false); assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, true); assert.equal(inputs[1].checked, true);
assert.equal(inputs[2].checked, true); assert.equal(inputs[2].checked, true);

@ -290,7 +290,7 @@ export default test({
` `
); );
component.selected_array = [[values[1], values[2]], [values[2]]]; component.selected_array = [[component.values[1], component.values[2]], [component.values[2]]];
assert.equal(inputs[0].checked, false); assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, true); assert.equal(inputs[1].checked, true);

@ -286,7 +286,7 @@ export default test({
` `
); );
component.selected_array = [[values[1], values[2]], [values[2]]]; component.selected_array = [[component.values[1], component.values[2]], [component.values[2]]];
assert.equal(inputs[0].checked, false); assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, true); assert.equal(inputs[1].checked, true);

@ -71,7 +71,7 @@ export default test({
assert.equal(inputs[1].checked, false); assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, false); assert.equal(inputs[2].checked, false);
component.selected = values[2]; component.selected = component.values[2];
assert.equal(inputs[0].checked, false); assert.equal(inputs[0].checked, false);
assert.equal(inputs[1].checked, false); assert.equal(inputs[1].checked, false);
assert.equal(inputs[2].checked, true); assert.equal(inputs[2].checked, true);

@ -21,7 +21,7 @@ export default test({
assert.equal(options[1].selected, false); assert.equal(options[1].selected, false);
assert.equal(options[0].value, ''); assert.equal(options[0].value, '');
component.foo = items[0]; component.foo = component.items[0];
assert.equal(options[0].selected, false); assert.equal(options[0].selected, false);
assert.equal(options[1].selected, true); assert.equal(options[1].selected, true);

@ -26,7 +26,7 @@ export default test({
assert.equal(options[0].value, ''); assert.equal(options[0].value, '');
assert.equal(select.checkValidity(), false); assert.equal(select.checkValidity(), false);
component.foo = items[0]; component.foo = component.items[0];
assert.equal(options[0].selected, false); assert.equal(options[0].selected, false);
assert.equal(options[1].selected, true); assert.equal(options[1].selected, true);

@ -1,7 +1,7 @@
import { test } from '../../test'; import { test } from '../../test';
/** @type {string[]} */ /** @type {string[]} */
const calls = []; let calls = [];
export default test({ export default test({
get props() { get props() {
@ -9,7 +9,7 @@ export default test({
}, },
before_test() { before_test() {
calls.length = 0; calls = [];
}, },
async test({ assert, component, target, window }) { async test({ assert, component, target, window }) {

@ -1,6 +1,6 @@
import { test } from '../../test'; import { test } from '../../test';
const items = [{}, {}]; const items = [{ id: 'a' }, { id: 'b' }];
export default test({ export default test({
get props() { get props() {
@ -13,7 +13,7 @@ export default test({
assert.equal(options[0].selected, true); assert.equal(options[0].selected, true);
assert.equal(options[1].selected, false); assert.equal(options[1].selected, false);
component.foo = items[1]; component.foo = component.items[1];
assert.equal(options[0].selected, false); assert.equal(options[0].selected, false);
assert.equal(options[1].selected, true); assert.equal(options[1].selected, true);

@ -1,12 +1,13 @@
import { test } from '../../test'; import { test } from '../../test';
import { flushSync } from 'svelte'; import { flushSync } from 'svelte';
import { log } from './log.js';
export default test({ export default test({
get props() { before_test() {
return { log: [] }; log.length = 0;
}, },
async test({ assert, target, component }) { async test({ assert, target }) {
const [b1] = target.querySelectorAll('button'); const [b1] = target.querySelectorAll('button');
flushSync(() => { flushSync(() => {
b1.click(); b1.click();
@ -20,6 +21,6 @@ export default test({
flushSync(() => { flushSync(() => {
b1.click(); b1.click();
}); });
assert.deepEqual(component.log, [0, 2, 4]); assert.deepEqual(log, [0, 2, 4]);
} }
}); });

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -1,5 +1,5 @@
<script> <script>
const {log} = $props(); import { log } from './log.js';
let count = $state(0); let count = $state(0);
const derived = $derived(Math.floor(count / 2)); const derived = $derived(Math.floor(count / 2));
@ -12,4 +12,4 @@
<button on:click={() => count += 1}> <button on:click={() => count += 1}>
clicks: {count} clicks: {count}
</button> </button>

@ -1,12 +1,13 @@
import { test } from '../../test'; import { test } from '../../test';
import { flushSync } from 'svelte'; import { flushSync } from 'svelte';
import { log } from './log.js';
export default test({ export default test({
get props() { before_test() {
return { log: [] }; log.length = 0;
}, },
async test({ assert, target, component }) { async test({ assert, target }) {
const [b1] = target.querySelectorAll('button'); const [b1] = target.querySelectorAll('button');
flushSync(() => { flushSync(() => {
b1.click(); b1.click();
@ -14,6 +15,6 @@ export default test({
flushSync(() => { flushSync(() => {
b1.click(); b1.click();
}); });
assert.deepEqual(component.log, ['init 0', 'cleanup 2', 'init 2', 'cleanup 4', 'init 4']); assert.deepEqual(log, ['init 0', 'cleanup 2', 'init 2', 'cleanup 4', 'init 4']);
} }
}); });

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -1,5 +1,5 @@
<script> <script>
const {log} = $props(); import { log } from './log.js';
let count = $state(0); let count = $state(0);

@ -1,12 +1,13 @@
import { test } from '../../test'; import { test } from '../../test';
import { flushSync } from 'svelte'; import { flushSync } from 'svelte';
import { log } from './log.js';
export default test({ export default test({
get props() { before_test() {
return { log: [] }; log.length = 0;
}, },
async test({ assert, target, component }) { async test({ assert, target }) {
const [b1] = target.querySelectorAll('button'); const [b1] = target.querySelectorAll('button');
flushSync(() => { flushSync(() => {
b1.click(); b1.click();
@ -14,6 +15,6 @@ export default test({
flushSync(() => { flushSync(() => {
b1.click(); b1.click();
}); });
assert.deepEqual(component.log, ['A', 'B', 'A', 'B', 'A', 'B']); assert.deepEqual(log, ['A', 'B', 'A', 'B', 'A', 'B']);
} }
}); });

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -1,5 +1,5 @@
<script> <script>
const {log} = $props(); import { log } from './log.js';
let s = $state(0); let s = $state(0);
let d = $derived(s) let d = $derived(s)

@ -1,12 +1,13 @@
import { flushSync } from 'svelte'; import { flushSync } from 'svelte';
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
export default test({ export default test({
get props() { before_test() {
return { log: [] }; log.length = 0;
}, },
async test({ assert, target, component }) { async test({ assert, target }) {
const [b1, b2, b3] = target.querySelectorAll('button'); const [b1, b2, b3] = target.querySelectorAll('button');
flushSync(() => { flushSync(() => {
@ -14,19 +15,19 @@ export default test({
b2.click(); b2.click();
}); });
assert.deepEqual(component.log, [0, 1]); assert.deepEqual(log, [0, 1]);
flushSync(() => { flushSync(() => {
b3.click(); b3.click();
}); });
assert.deepEqual(component.log, [0, 1, 'cleanup 1', 'cleanup 2']); assert.deepEqual(log, [0, 1, 'cleanup 1', 'cleanup 2']);
flushSync(() => { flushSync(() => {
b1.click(); b1.click();
b2.click(); b2.click();
}); });
assert.deepEqual(component.log, [0, 1, 'cleanup 1', 'cleanup 2']); assert.deepEqual(log, [0, 1, 'cleanup 1', 'cleanup 2']);
} }
}); });

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -1,5 +1,5 @@
<script> <script>
let { log} = $props(); import { log } from './log.js';
let x = $state(0); let x = $state(0);
let y = $state(0); let y = $state(0);

@ -1,16 +1,17 @@
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
export default test({ export default test({
get props() { before_test() {
return { log: [] }; log.length = 0;
}, },
async test({ assert, target, component }) { async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button'); const [b1, b2] = target.querySelectorAll('button');
b1.click(); b1.click();
b2.click(); b2.click();
await Promise.resolve(); await Promise.resolve();
assert.deepEqual(component.log, [0, 1]); assert.deepEqual(log, [0, 1]);
} }
}); });

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -1,5 +1,5 @@
<script> <script>
let { log} = $props(); import { log } from './log.js';
let x = $state(0); let x = $state(0);
let y = $state(0); let y = $state(0);

@ -1,16 +1,17 @@
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
export default test({ export default test({
get props() { before_test() {
return { log: [] }; log.length = 0;
}, },
async test({ assert, target, component }) { async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button'); const [b1, b2] = target.querySelectorAll('button');
b1.click(); b1.click();
b2.click(); b2.click();
await Promise.resolve(); await Promise.resolve();
assert.deepEqual(component.log, ['first0', 'second0', 'first1', 'second1']); assert.deepEqual(log, ['first0', 'second0', 'first1', 'second1']);
} }
}); });

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -1,5 +1,5 @@
<script> <script>
let { log} = $props(); import { log } from './log.js';
let x = $state(0); let x = $state(0);
let y = $state(0); let y = $state(0);

@ -1,21 +1,23 @@
<script> <script>
let { index, n, order } = $props(); import { log } from './log.js';
let { index, n } = $props();
function logRender () { function logRender () {
order.push(`${index}: render ${n}`); log.push(`${index}: render ${n}`);
return index; return index;
} }
$effect.pre(() => { $effect.pre(() => {
order.push(`${index}: $effect.pre ${n}`); log.push(`${index}: $effect.pre ${n}`);
}); });
$effect.pre(() => { $effect.pre(() => {
order.push(`${index}: $effect.pre (2) ${n}`); log.push(`${index}: $effect.pre (2) ${n}`);
}); });
$effect(() => { $effect(() => {
order.push(`${index}: $effect ${n}`); log.push(`${index}: $effect ${n}`);
}); });
</script> </script>

@ -1,24 +1,17 @@
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
/**
* @type {any[]}
*/
let order = [];
let n = 0;
export default test({ export default test({
get props() { get props() {
return { return { n: 0 };
order,
n
};
}, },
before_test() { before_test() {
order.length = 0; log.length = 0;
n = 0;
}, },
async test({ assert, compileOptions, component }) {
assert.deepEqual(order, [ async test({ assert, component }) {
assert.deepEqual(log, [
'parent: $effect.pre 0', 'parent: $effect.pre 0',
'parent: $effect.pre (2) 0', 'parent: $effect.pre (2) 0',
'parent: render 0', 'parent: render 0',
@ -37,11 +30,11 @@ export default test({
'parent: $effect 0' 'parent: $effect 0'
]); ]);
order.length = 0; log.length = 0;
component.n += 1; component.n += 1;
assert.deepEqual(order, [ assert.deepEqual(log, [
'parent: $effect.pre 1', 'parent: $effect.pre 1',
'parent: $effect.pre (2) 1', 'parent: $effect.pre (2) 1',
'parent: render 1', 'parent: render 1',

@ -1,30 +1,31 @@
<script> <script>
import Item from './Item.svelte'; import Item from './Item.svelte';
import { log } from './log.js';
let { n = 0, order } = $props(); let { n = 0 } = $props();
function logRender () { function logRender () {
order.push(`parent: render ${n}`); log.push(`parent: render ${n}`);
return 'parent'; return 'parent';
} }
$effect.pre(() => { $effect.pre(() => {
order.push(`parent: $effect.pre ${n}`); log.push(`parent: $effect.pre ${n}`);
}); });
$effect.pre(() => { $effect.pre(() => {
order.push(`parent: $effect.pre (2) ${n}`); log.push(`parent: $effect.pre (2) ${n}`);
}); });
$effect(() => { $effect(() => {
order.push(`parent: $effect ${n}`); log.push(`parent: $effect ${n}`);
}); });
</script> </script>
{logRender()} {logRender()}
<ul> <ul>
{#each [1,2,3] as index} {#each [1,2,3] as index}
<Item {index} {n} {order} /> <Item {index} {n} />
{/each} {/each}
</ul> </ul>

@ -1,17 +1,19 @@
<script> <script>
let { index, n, order } = $props(); import { log } from './log.js';
let { index, n } = $props();
function logRender () { function logRender () {
order.push(`${index}: render ${n}`); log.push(`${index}: render ${n}`);
return index; return index;
} }
$effect.pre(() => { $effect.pre(() => {
order.push(`${index}: $effect.pre ${n}`); log.push(`${index}: $effect.pre ${n}`);
}); });
$effect(() => { $effect(() => {
order.push(`${index}: $effect ${n}`); log.push(`${index}: $effect ${n}`);
}); });
</script> </script>

@ -1,24 +1,17 @@
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
/**
* @type {any[]}
*/
let order = [];
let n = 0;
export default test({ export default test({
get props() { get props() {
return { return { n: 0 };
order,
n
};
}, },
before_test() { before_test() {
order.length = 0; log.length = 0;
n = 0;
}, },
async test({ assert, compileOptions, component }) {
assert.deepEqual(order, [ async test({ assert, component }) {
assert.deepEqual(log, [
'parent: render 0', 'parent: render 0',
'1: $effect.pre 0', '1: $effect.pre 0',
'1: render 0', '1: render 0',
@ -32,11 +25,11 @@ export default test({
'parent: $effect 0' 'parent: $effect 0'
]); ]);
order.length = 0; log.length = 0;
component.n += 1; component.n += 1;
assert.deepEqual(order, [ assert.deepEqual(log, [
'parent: render 1', 'parent: render 1',
'1: $effect.pre 1', '1: $effect.pre 1',
'1: render 1', '1: render 1',

@ -1,22 +1,23 @@
<script> <script>
import Item from './Item.svelte'; import Item from './Item.svelte';
import { log } from './log.js';
let { n = 0, order } = $props(); let { n = 0 } = $props();
function logRender () { function logRender () {
order.push(`parent: render ${n}`); log.push(`parent: render ${n}`);
return 'parent'; return 'parent';
} }
$effect(() => { $effect(() => {
order.push(`parent: $effect ${n}`); log.push(`parent: $effect ${n}`);
}); });
</script> </script>
{logRender()} {logRender()}
<ul> <ul>
{#each [1,2,3] as index} {#each [1,2,3] as index}
<Item {index} {n} {order} /> <Item {index} {n} />
{/each} {/each}
</ul> </ul>

@ -1,21 +1,23 @@
<script> <script>
let { index, n, order } = $props(); import { log } from './log.js';
let { index, n } = $props();
function logRender () { function logRender () {
order.push(`${index}: render ${n}`); log.push(`${index}: render ${n}`);
return index; return index;
} }
$effect.pre(() => { $effect.pre(() => {
order.push(`${index}: $effect.pre ${n}`); log.push(`${index}: $effect.pre ${n}`);
$effect.pre(() => { $effect.pre(() => {
order.push(`${index}: nested $effect.pre ${n}`); log.push(`${index}: nested $effect.pre ${n}`);
}); });
}); });
$effect(() => { $effect(() => {
order.push(`${index}: $effect ${n}`); log.push(`${index}: $effect ${n}`);
}); });
</script> </script>

@ -1,24 +1,17 @@
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
/**
* @type {any[]}
*/
let order = [];
let n = 0;
export default test({ export default test({
get props() { get props() {
return { return { n: 0 };
order,
n
};
}, },
before_test() { before_test() {
order.length = 0; log.length = 0;
n = 0;
}, },
async test({ assert, compileOptions, component }) {
assert.deepEqual(order, [ async test({ assert, component }) {
assert.deepEqual(log, [
'parent: $effect.pre 0', 'parent: $effect.pre 0',
'parent: nested $effect.pre 0', 'parent: nested $effect.pre 0',
'parent: render 0', 'parent: render 0',
@ -37,11 +30,11 @@ export default test({
'parent: $effect 0' 'parent: $effect 0'
]); ]);
order.length = 0; log.length = 0;
component.n += 1; component.n += 1;
assert.deepEqual(order, [ assert.deepEqual(log, [
'parent: $effect.pre 1', 'parent: $effect.pre 1',
'parent: nested $effect.pre 1', 'parent: nested $effect.pre 1',
'parent: render 1', 'parent: render 1',

@ -1,30 +1,31 @@
<script> <script>
import Item from './Item.svelte'; import Item from './Item.svelte';
import { log } from './log.js';
let { n = 0, order } = $props(); let { n = 0 } = $props();
function logRender () { function logRender () {
order.push(`parent: render ${n}`); log.push(`parent: render ${n}`);
return 'parent'; return 'parent';
} }
$effect.pre(() => { $effect.pre(() => {
order.push(`parent: $effect.pre ${n}`); log.push(`parent: $effect.pre ${n}`);
$effect.pre(() => { $effect.pre(() => {
order.push(`parent: nested $effect.pre ${n}`); log.push(`parent: nested $effect.pre ${n}`);
}); });
}); });
$effect(() => { $effect(() => {
order.push(`parent: $effect ${n}`); log.push(`parent: $effect ${n}`);
}); });
</script> </script>
{logRender()} {logRender()}
<ul> <ul>
{#each [1,2,3] as index} {#each [1,2,3] as index}
<Item {index} {n} {order} /> <Item {index} {n} />
{/each} {/each}
</ul> </ul>

@ -1,17 +1,19 @@
<script> <script>
let { index, n, order } = $props(); import { log } from './log.js';
let { index, n } = $props();
function logRender () { function logRender () {
order.push(`${index}: render ${n}`); log.push(`${index}: render ${n}`);
return index; return index;
} }
$effect.pre(() => { $effect.pre(() => {
order.push(`${index}: $effect.pre ${n}`); log.push(`${index}: $effect.pre ${n}`);
}); });
$effect(() => { $effect(() => {
order.push(`${index}: $effect ${n}`); log.push(`${index}: $effect ${n}`);
}); });
</script> </script>

@ -1,24 +1,17 @@
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
/**
* @type {any[]}
*/
let order = [];
let n = 0;
export default test({ export default test({
get props() { get props() {
return { return { n: 0 };
order,
n
};
}, },
before_test() { before_test() {
order.length = 0; log.length = 0;
n = 0;
}, },
async test({ assert, compileOptions, component }) {
assert.deepEqual(order, [ async test({ assert, component }) {
assert.deepEqual(log, [
'parent: $effect.pre 0', 'parent: $effect.pre 0',
'parent: render 0', 'parent: render 0',
'1: $effect.pre 0', '1: $effect.pre 0',
@ -33,11 +26,11 @@ export default test({
'parent: $effect 0' 'parent: $effect 0'
]); ]);
order.length = 0; log.length = 0;
component.n += 1; component.n += 1;
assert.deepEqual(order, [ assert.deepEqual(log, [
'parent: $effect.pre 1', 'parent: $effect.pre 1',
'parent: render 1', 'parent: render 1',
'1: $effect.pre 1', '1: $effect.pre 1',

@ -1,26 +1,27 @@
<script> <script>
import Item from './Item.svelte'; import Item from './Item.svelte';
import { log } from './log.js';
let { n = 0, order } = $props(); let { n = 0 } = $props();
function logRender () { function logRender () {
order.push(`parent: render ${n}`); log.push(`parent: render ${n}`);
return 'parent'; return 'parent';
} }
$effect.pre(() => { $effect.pre(() => {
order.push(`parent: $effect.pre ${n}`); log.push(`parent: $effect.pre ${n}`);
}); });
$effect(() => { $effect(() => {
order.push(`parent: $effect ${n}`); log.push(`parent: $effect ${n}`);
}); });
</script> </script>
{logRender()} {logRender()}
<ul> <ul>
{#each [1,2,3] as index} {#each [1,2,3] as index}
<Item {index} {n} {order} /> <Item {index} {n} />
{/each} {/each}
</ul> </ul>

@ -1,13 +1,14 @@
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
export default test({ export default test({
get props() { before_test() {
return { log: [] }; log.length = 0;
}, },
async test({ assert, target, component }) { async test({ assert }) {
await Promise.resolve(); await Promise.resolve();
await Promise.resolve(); await Promise.resolve();
assert.deepEqual(component.log, ['a1: ', true, 'b1: ', true]); assert.deepEqual(log, ['a1: ', true, 'b1: ', true]);
} }
}); });

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -1,12 +1,13 @@
<script> <script>
import { log } from './log.js';
let a1 = $state(); let a1 = $state();
let b1 = $state(); let b1 = $state();
const {log} = $props();
$effect(() => { $effect(() => {
log.push('a1: ', a1); log.push('a1: ', a1);
}); });
$effect(() => { $effect(() => {
log.push('b1: ', b1); log.push('b1: ', b1);
}); });

@ -1,12 +1,13 @@
import { flushSync } from 'svelte'; import { flushSync } from 'svelte';
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
export default test({ export default test({
get props() { before_test() {
return { log: [] }; log.length = 0;
}, },
async test({ assert, target, component }) { async test({ assert, target }) {
const [b1] = target.querySelectorAll('button'); const [b1] = target.querySelectorAll('button');
flushSync(() => { flushSync(() => {
@ -16,7 +17,7 @@ export default test({
b1.click(); b1.click();
}); });
assert.deepEqual(component.log, [ assert.deepEqual(log, [
'Outer Effect Start (0)', 'Outer Effect Start (0)',
'Outer Effect End (0)', 'Outer Effect End (0)',
'Inner Effect (0)', 'Inner Effect (0)',

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -1,11 +1,12 @@
<script> <script>
const {log} = $props(); import { log } from './log.js';
let count = $state(0); let count = $state(0);
function increment() { function increment() {
count += 1; count += 1;
} }
$effect.pre(() => { $effect.pre(() => {
log.push(`Outer Effect Start (${count})`) log.push(`Outer Effect Start (${count})`)
@ -19,4 +20,4 @@
<button on:click={increment}> <button on:click={increment}>
Count: {count} Count: {count}
</button> </button>

@ -1,16 +1,17 @@
import { test } from '../../test'; import { test } from '../../test';
import { log } from './log.js';
export default test({ export default test({
get props() { before_test() {
return { log: [] }; log.length = 0;
}, },
async test({ assert, target, component }) { async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button'); const [b1, b2] = target.querySelectorAll('button');
b1.click(); b1.click();
b2.click(); b2.click();
await Promise.resolve(); await Promise.resolve();
assert.deepEqual(component.log, [0, 1]); assert.deepEqual(log, [0, 1]);
} }
}); });

@ -0,0 +1,2 @@
/** @type {any[]} */
export const log = [];

@ -1,5 +1,5 @@
<script> <script>
let { log} = $props(); import { log } from './log.js';
let x = $state(0); let x = $state(0);
let y = $state(0); let y = $state(0);

@ -6,12 +6,12 @@ import * as $ from "svelte/internal";
export default function Svelte_element($$anchor, $$props) { export default function Svelte_element($$anchor, $$props) {
$.push($$props, true); $.push($$props, true);
let tag = $.prop_source($$props, "tag", 3, 'hr'); let tag = $.prop($$props, "tag", 3, 'hr');
/* Init */ /* Init */
var fragment = $.comment($$anchor); var fragment = $.comment($$anchor);
var node = $.child_frag(fragment); var node = $.child_frag(fragment);
$.element(node, () => $.get(tag)); $.element(node, () => tag());
$.close_frag($$anchor, fragment); $.close_frag($$anchor, fragment);
$.pop(); $.pop();
} }

Loading…
Cancel
Save