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;
properties.push(
b.get(key, [b.return(b.call('$.get', b.id(name)))]),
b.set(key, [b.stmt(b.call('$.set_sync', b.id(name), b.id('$$value')))])
b.get(key, [b.return(b.call(b.id(name)))]),
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 { error } from '../../../errors.js';
import {
PROPS_CALL_DEFAULT_VALUE,
PROPS_IS_LAZY_INITIAL,
PROPS_IS_IMMUTABLE,
PROPS_IS_RUNES
PROPS_IS_RUNES,
PROPS_IS_UPDATED
} from '../../../../constants.js';
/**
@ -73,12 +74,14 @@ export function serialize_get_binding(node, state) {
}
if (
!state.analysis.accessors &&
!(state.analysis.immutable ? binding.reassigned : binding.mutated) &&
!binding.initial
state.analysis.accessors ||
(state.analysis.immutable ? binding.reassigned : binding.mutated) ||
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') {
@ -89,7 +92,6 @@ export function serialize_get_binding(node, state) {
(binding.kind === 'state' &&
(!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) ||
binding.kind === 'derived' ||
binding.kind === 'prop' ||
binding.kind === 'legacy_reactive'
) {
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 serialize = () => {
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);
} else {
return b.call(
@ -232,15 +237,27 @@ export function serialize_set_binding(node, context, fallback) {
b.call('$' + left_name)
);
} else if (!state.analysis.runes) {
return b.call(
'$.mutate',
b.id(left_name),
b.assignment(
node.operator,
/** @type {import('estree').Pattern} */ (visit(node.left)),
value
)
);
if (binding.kind === 'prop') {
return b.call(
left,
b.assignment(
node.operator,
/** @type {import('estree').Pattern} */ (visit(node.left)),
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 {
return b.assignment(
node.operator,
@ -345,12 +362,13 @@ export function serialize_hoistable_params(node, context) {
}
/**
* @param {import('#compiler').Binding} binding
* @param {import('./types').ComponentClientTransformState} state
* @param {string} name
* @param {import('estree').Expression | null} [initial]
* @returns
*/
export function get_prop_source(state, name, initial) {
export function get_prop_source(binding, state, name, initial) {
/** @type {import('estree').Expression[]} */
const args = [b.id('$$props'), b.literal(name)];
@ -364,6 +382,13 @@ export function get_prop_source(state, name, initial) {
flags |= PROPS_IS_RUNES;
}
if (
state.analysis.accessors ||
(state.analysis.immutable ? binding.reassigned : binding.mutated)
) {
flags |= PROPS_IS_UPDATED;
}
/** @type {import('estree').Expression | undefined} */
let arg;
@ -382,7 +407,7 @@ export function get_prop_source(state, name, 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);
}
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 is_store = binding?.kind === 'store_sub';
const name = is_store ? argument.name.slice(1) : argument.name;
// use runtime functions for smaller output
if (
binding?.kind === 'state' ||
@ -53,18 +54,28 @@ export const global_visitors = {
binding?.kind === 'prop' ||
is_store
) {
let fn = node.operator === '++' ? '$.increment' : '$.decrement';
/** @type {import('estree').Expression[]} */
const args = [];
let fn = '$.update';
if (node.prefix) fn += '_pre';
if (is_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 {
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 (
argument.type === 'MemberExpression' &&
argument.object.type === 'ThisExpression' &&

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

@ -7,7 +7,8 @@ export const EACH_IS_IMMUTABLE = 1 << 6;
export const PROPS_IS_IMMUTABLE = 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 */
export const DelegatedEvents = [

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

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

@ -2,16 +2,20 @@ import { DEV } from 'esm-env';
import { subscribe_to_store } from '../../store/utils.js';
import { EMPTY_FUNC, run_all } from '../common.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 { observe, proxy } from './proxy/proxy.js';
import { proxy } from './proxy/proxy.js';
export const SOURCE = 1;
export const DERIVED = 1 << 1;
export const EFFECT = 1 << 2;
export const PRE_EFFECT = 1 << 3;
export const RENDER_EFFECT = 1 << 4;
export const SYNC_EFFECT = 1 << 5;
const MANAGED = 1 << 6;
const UNOWNED = 1 << 7;
export const CLEAN = 1 << 8;
@ -20,7 +24,7 @@ export const MAYBE_DIRTY = 1 << 10;
export const INERT = 1 << 11;
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_SYNC = 1;
@ -536,7 +540,7 @@ function process_microtask() {
*/
export function schedule_effect(signal, sync) {
const flags = signal.f;
if (sync || (flags & SYNC_EFFECT) !== 0) {
if (sync) {
execute_effect(signal);
set_signal_status(signal, CLEAN);
} else {
@ -1284,14 +1288,6 @@ export function invalidate_effect(init) {
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
* @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.
* It is used whenever the compiler sees that the component writes to the prop.
*
* - 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
* It is used whenever the compiler sees that the component writes to the prop, or when it has a default prop_value.
* @template V
* @param {Record<string, unknown>} props
* @param {string} key
* @param {number} flags
* @param {V | (() => V)} [default_value]
* @returns {import('./types.js').Signal<V> | (() => V)}
* @param {V | (() => V)} [initial]
* @returns {(() => V | ((arg: V) => V) | ((arg: V, mutation: boolean) => V))}
*/
export function prop_source(props, key, flags, default_value) {
const call_default_value = (flags & PROPS_CALL_DEFAULT_VALUE) !== 0;
const immutable = (flags & PROPS_IS_IMMUTABLE) !== 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;
export function prop(props, key, flags, initial) {
var immutable = (flags & PROPS_IS_IMMUTABLE) !== 0;
var runes = (flags & PROPS_IS_RUNES) !== 0;
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
throw new Error('Cannot use fallback values with bind:');
}
if (should_set_default_value) {
value =
// @ts-expect-error would need a cumbersome method overload to type this
call_default_value ? default_value() : default_value;
var prop_value = /** @type {V} */ (props[key]);
if (prop_value === undefined && initial !== undefined) {
// @ts-expect-error would need a cumbersome method overload to type this
if ((flags & PROPS_IS_LAZY_INITIAL) !== 0) initial = initial();
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.
// Needs special equality checking because the prop in the
// parent could be changed through `foo.bar = 'new value'`.
let ignore_next1 = false;
let ignore_next2 = false;
let did_update_to_defined = !should_set_default_value;
// easy mode — prop is never written to
if ((flags & PROPS_IS_UPDATED) === 0) {
return getter;
}
let mount = true;
sync_effect(() => {
observe(props);
// intermediate mode — prop is written to, but the parent component had
// `bind:foo` which means we can just call `$$props.foo = value` directly
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
const propagating_value = props[key];
if (mount) {
mount = false;
return;
}
if (ignore_next1) {
ignore_next1 = false;
return;
// hard mode. this is where it gets ugly — the value in the child should
// synchronize with the parent, but it should also be possible to temporarily
// set the value to something else locally.
var from_child = false;
var was_from_child = false;
// The derived returns the current value. The underlying mutable
// source is written to from various places to persist this value.
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 (
// Ensure that updates from undefined to undefined are ignored
(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));
}
was_from_child = false;
return (inner_current_value.v = parent_value);
});
if (update_bound_prop !== undefined) {
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;
}
if (!immutable) current_value.e = safe_equal;
ignore_next1 = true;
did_update_to_defined = true;
untrack(() => update_bound_prop(propagating_value));
});
}
return function (/** @type {V} */ value, mutation = false) {
var current = get(current_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);
}
/**
* @param {boolean} immutable
* @param {unknown} a
* @param {unknown} b
* @returns {boolean}
*/
function not_equal(immutable, a, b) {
return immutable ? immutable_not_equal(a, b) : safe_not_equal(a, b);
}
if (arguments.length > 0) {
if (mutation || (immutable ? value !== current : safe_not_equal(value, current))) {
from_child = true;
set(inner_current_value, mutation ? current : value);
get(current_value); // force a synchronisation immediately
}
/**
* @param {unknown} a
* @param {unknown} b
* @returns {boolean}
*/
function immutable_not_equal(a, b) {
// eslint-disable-next-line eqeqeq
return a != a ? b == b : a !== b;
return value;
}
return current;
};
}
/**
@ -1584,82 +1570,67 @@ export function bubble_event($$props, event) {
/**
* @param {import('./types.js').Signal<number>} signal
* @param {1 | -1} [d]
* @returns {number}
*/
export function increment(signal) {
export function update(signal, d = 1) {
const value = get(signal);
set_signal_value(signal, value + 1);
set_signal_value(signal, value + d);
return value;
}
/**
* @param {import('./types.js').Store<number>} store
* @param {number} store_value
* @returns {number}
*/
export function increment_store(store, store_value) {
store.set(store_value + 1);
return store_value;
}
/**
* @param {import('./types.js').Signal<number>} signal
* @param {((value?: number) => number)} fn
* @param {1 | -1} [d]
* @returns {number}
*/
export function decrement(signal) {
const value = get(signal);
set_signal_value(signal, value - 1);
export function update_prop(fn, d = 1) {
const value = fn();
fn(value + d);
return value;
}
/**
* @param {import('./types.js').Store<number>} store
* @param {number} store_value
* @param {1 | -1} [d]
* @returns {number}
*/
export function decrement_store(store, store_value) {
store.set(store_value - 1);
export function update_store(store, store_value, d = 1) {
store.set(store_value + d);
return store_value;
}
/**
* @param {import('./types.js').Signal<number>} signal
* @param {1 | -1} [d]
* @returns {number}
*/
export function increment_pre(signal) {
const value = get(signal) + 1;
export function update_pre(signal, d = 1) {
const value = get(signal) + d;
set_signal_value(signal, value);
return value;
}
/**
* @param {import('./types.js').Store<number>} store
* @param {number} store_value
* @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
* @param {((value?: number) => number)} fn
* @param {1 | -1} [d]
* @returns {number}
*/
export function decrement_pre(signal) {
const value = get(signal) - 1;
set_signal_value(signal, value);
export function update_pre_prop(fn, d = 1) {
const value = fn() + d;
fn(value);
return value;
}
/**
* @param {import('./types.js').Store<number>} store
* @param {number} store_value
* @param {1 | -1} [d]
* @returns {number}
*/
export function decrement_pre_store(store, store_value) {
const value = store_value - 1;
export function update_pre_store(store, store_value, d = 1) {
const value = store_value + d;
store.set(value);
return value;
}

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

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

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

@ -71,7 +71,7 @@ export default test({
assert.equal(inputs[1].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[1].checked, false);
assert.equal(inputs[2].checked, true);

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

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

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

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

@ -1,12 +1,13 @@
import { test } from '../../test';
import { flushSync } from 'svelte';
import { log } from './log.js';
export default test({
get props() {
return { log: [] };
before_test() {
log.length = 0;
},
async test({ assert, target, component }) {
async test({ assert, target }) {
const [b1] = target.querySelectorAll('button');
flushSync(() => {
b1.click();
@ -20,6 +21,6 @@ export default test({
flushSync(() => {
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>
const {log} = $props();
import { log } from './log.js';
let count = $state(0);
const derived = $derived(Math.floor(count / 2));
@ -12,4 +12,4 @@
<button on:click={() => count += 1}>
clicks: {count}
</button>
</button>

@ -1,12 +1,13 @@
import { test } from '../../test';
import { flushSync } from 'svelte';
import { log } from './log.js';
export default test({
get props() {
return { log: [] };
before_test() {
log.length = 0;
},
async test({ assert, target, component }) {
async test({ assert, target }) {
const [b1] = target.querySelectorAll('button');
flushSync(() => {
b1.click();
@ -14,6 +15,6 @@ export default test({
flushSync(() => {
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>
const {log} = $props();
import { log } from './log.js';
let count = $state(0);

@ -1,12 +1,13 @@
import { test } from '../../test';
import { flushSync } from 'svelte';
import { log } from './log.js';
export default test({
get props() {
return { log: [] };
before_test() {
log.length = 0;
},
async test({ assert, target, component }) {
async test({ assert, target }) {
const [b1] = target.querySelectorAll('button');
flushSync(() => {
b1.click();
@ -14,6 +15,6 @@ export default test({
flushSync(() => {
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>
const {log} = $props();
import { log } from './log.js';
let s = $state(0);
let d = $derived(s)

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

@ -1,16 +1,17 @@
import { test } from '../../test';
import { log } from './log.js';
export default test({
get props() {
return { log: [] };
before_test() {
log.length = 0;
},
async test({ assert, target, component }) {
async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
b2.click();
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>
let { log} = $props();
import { log } from './log.js';
let x = $state(0);
let y = $state(0);

@ -1,16 +1,17 @@
import { test } from '../../test';
import { log } from './log.js';
export default test({
get props() {
return { log: [] };
before_test() {
log.length = 0;
},
async test({ assert, target, component }) {
async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
b2.click();
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>
let { log} = $props();
import { log } from './log.js';
let x = $state(0);
let y = $state(0);

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,13 +1,14 @@
import { test } from '../../test';
import { log } from './log.js';
export default test({
get props() {
return { log: [] };
before_test() {
log.length = 0;
},
async test({ assert, target, component }) {
async test({ assert }) {
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>
import { log } from './log.js';
let a1 = $state();
let b1 = $state();
const {log} = $props();
$effect(() => {
log.push('a1: ', a1);
});
$effect(() => {
log.push('b1: ', b1);
});

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

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

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

@ -1,16 +1,17 @@
import { test } from '../../test';
import { log } from './log.js';
export default test({
get props() {
return { log: [] };
before_test() {
log.length = 0;
},
async test({ assert, target, component }) {
async test({ assert, target }) {
const [b1, b2] = target.querySelectorAll('button');
b1.click();
b2.click();
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>
let { log} = $props();
import { log } from './log.js';
let x = $state(0);
let y = $state(0);

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

Loading…
Cancel
Save