Merge branch 'upstream-master' into introducing-runes

introducing-runes
Simon Holthausen 2 years ago
commit 5afc2dffc0

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: css sourcemap generation with unicode filenames

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: head duplication when binding is present

@ -1,5 +0,0 @@
---
'svelte': patch
---
fix: take custom attribute name into account when reflecting property

@ -106,11 +106,13 @@ An element or component can have multiple spread attributes, interspersed with r
## Text expressions ## Text expressions
A JavaScript expression can be included as text by surrounding it with curly braces.
```svelte ```svelte
{expression} {expression}
``` ```
Text can also contain JavaScript expressions: Curly braces can be included in a Svelte template by using their [HTML entity](https://developer.mozilla.org/docs/Glossary/Entity) strings: `{`, `{`, or `{` for `{` and `}`, `}`, or `}` for `}`.
> If you're using a regular expression (`RegExp`) [literal notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor), you'll need to wrap it in parentheses. > If you're using a regular expression (`RegExp`) [literal notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#literal_notation_and_constructor), you'll need to wrap it in parentheses.

@ -5,14 +5,17 @@ title: Logic blocks
## {#if ...} ## {#if ...}
```svelte ```svelte
<!--- copy: false --->
{#if expression}...{/if} {#if expression}...{/if}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#if expression}...{:else if expression}...{/if} {#if expression}...{:else if expression}...{/if}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#if expression}...{:else}...{/if} {#if expression}...{:else}...{/if}
``` ```
@ -41,22 +44,27 @@ Additional conditions can be added with `{:else if expression}`, optionally endi
## {#each ...} ## {#each ...}
```svelte ```svelte
<!--- copy: false --->
{#each expression as name}...{/each} {#each expression as name}...{/each}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#each expression as name, index}...{/each} {#each expression as name, index}...{/each}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#each expression as name (key)}...{/each} {#each expression as name (key)}...{/each}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#each expression as name, index (key)}...{/each} {#each expression as name, index (key)}...{/each}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#each expression as name}...{:else}...{/each} {#each expression as name}...{:else}...{/each}
``` ```
@ -125,18 +133,22 @@ Since Svelte 4 it is possible to iterate over iterables like `Map` or `Set`. Ite
## {#await ...} ## {#await ...}
```svelte ```svelte
<!--- copy: false --->
{#await expression}...{:then name}...{:catch name}...{/await} {#await expression}...{:then name}...{:catch name}...{/await}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#await expression}...{:then name}...{/await} {#await expression}...{:then name}...{/await}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#await expression then name}...{/await} {#await expression then name}...{/await}
``` ```
```svelte ```svelte
<!--- copy: false --->
{#await expression catch name}...{/await} {#await expression catch name}...{/await}
``` ```
@ -186,6 +198,7 @@ Similarly, if you only want to show the error state, you can omit the `then` blo
## {#key ...} ## {#key ...}
```svelte ```svelte
<!--- copy: false --->
{#key expression}...{/key} {#key expression}...{/key}
``` ```

@ -5,6 +5,7 @@ title: Special tags
## {@html ...} ## {@html ...}
```svelte ```svelte
<!--- copy: false --->
{@html expression} {@html expression}
``` ```
@ -24,10 +25,12 @@ The expression should be valid standalone HTML — `{@html "<div>"}content{@html
## {@debug ...} ## {@debug ...}
```svelte ```svelte
<!--- copy: false --->
{@debug} {@debug}
``` ```
```svelte ```svelte
<!--- copy: false --->
{@debug var1, var2, ..., varN} {@debug var1, var2, ..., varN}
``` ```
@ -65,6 +68,7 @@ The `{@debug}` tag without any arguments will insert a `debugger` statement that
## {@const ...} ## {@const ...}
```svelte ```svelte
<!--- copy: false --->
{@const assignment} {@const assignment}
``` ```

@ -7,10 +7,12 @@ As well as attributes, elements can have _directives_, which control the element
## on:_eventname_ ## on:_eventname_
```svelte ```svelte
<!--- copy: false --->
on:eventname={handler} on:eventname={handler}
``` ```
```svelte ```svelte
<!--- copy: false --->
on:eventname|modifiers={handler} on:eventname|modifiers={handler}
``` ```
@ -72,7 +74,6 @@ If the `on:` directive is used without a value, the component will _forward_ the
It's possible to have multiple event listeners for the same event: It's possible to have multiple event listeners for the same event:
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
let counter = 0; let counter = 0;
function increment() { function increment() {
@ -91,6 +92,7 @@ It's possible to have multiple event listeners for the same event:
## bind:_property_ ## bind:_property_
```svelte ```svelte
<!--- copy: false --->
bind:property={variable} bind:property={variable}
``` ```
@ -186,6 +188,8 @@ Elements with the `contenteditable` attribute support the following bindings:
There are slight differences between each of these, read more about them [here](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#Differences_from_innerText). There are slight differences between each of these, read more about them [here](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#Differences_from_innerText).
<!-- for some reason puts the comment and html on same line -->
<!-- prettier-ignore -->
```svelte ```svelte
<div contenteditable="true" bind:innerHTML={html} /> <div contenteditable="true" bind:innerHTML={html} />
``` ```
@ -273,13 +277,13 @@ Block-level elements have 4 read-only bindings, measured using a technique simil
## bind:group ## bind:group
```svelte ```svelte
<!--- copy: false --->
bind:group={variable} bind:group={variable}
``` ```
Inputs that work together can use `bind:group`. Inputs that work together can use `bind:group`.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
let tortilla = 'Plain'; let tortilla = 'Plain';
@ -304,13 +308,13 @@ Inputs that work together can use `bind:group`.
## bind:this ## bind:this
```svelte ```svelte
<!--- copy: false --->
bind:this={dom_node} bind:this={dom_node}
``` ```
To get a reference to a DOM node, use `bind:this`. To get a reference to a DOM node, use `bind:this`.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -329,10 +333,12 @@ To get a reference to a DOM node, use `bind:this`.
## class:_name_ ## class:_name_
```svelte ```svelte
<!--- copy: false --->
class:name={value} class:name={value}
``` ```
```svelte ```svelte
<!--- copy: false --->
class:name class:name
``` ```
@ -393,14 +399,17 @@ When `style:` directives are combined with `style` attributes, the directives wi
## use:_action_ ## use:_action_
```svelte ```svelte
<!--- copy: false --->
use:action use:action
``` ```
```svelte ```svelte
<!--- copy: false --->
use:action={parameters} use:action={parameters}
``` ```
```ts ```ts
/// copy: false
// @noErrors // @noErrors
action = (node: HTMLElement, parameters: any) => { action = (node: HTMLElement, parameters: any) => {
update?: (parameters: any) => void, update?: (parameters: any) => void,
@ -411,7 +420,6 @@ action = (node: HTMLElement, parameters: any) => {
Actions are functions that are called when an element is created. They can return an object with a `destroy` method that is called after the element is unmounted: Actions are functions that are called when an element is created. They can return an object with a `destroy` method that is called after the element is unmounted:
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
/** @type {import('svelte/action').Action} */ /** @type {import('svelte/action').Action} */
function foo(node) { function foo(node) {
@ -433,7 +441,6 @@ An action can have a parameter. If the returned value has an `update` method, it
> Don't worry about the fact that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition. > Don't worry about the fact that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
export let bar; export let bar;
@ -461,30 +468,37 @@ Read more in the [`svelte/action`](/docs/svelte-action) page.
## transition:_fn_ ## transition:_fn_
```svelte ```svelte
<!--- copy: false --->
transition:fn transition:fn
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn={params} transition:fn={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn|global transition:fn|global
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn|global={params} transition:fn|global={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn|local transition:fn|local
``` ```
```svelte ```svelte
<!--- copy: false --->
transition:fn|local={params} transition:fn|local={params}
``` ```
```js ```js
/// copy: false
// @noErrors // @noErrors
transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => { transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => {
delay?: number, delay?: number,
@ -544,7 +558,6 @@ The `t` argument passed to `css` is a value between `0` and `1` after the `easin
The function is called repeatedly _before_ the transition begins, with different `t` and `u` arguments. The function is called repeatedly _before_ the transition begins, with different `t` and `u` arguments.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
import { elasticOut } from 'svelte/easing'; import { elasticOut } from 'svelte/easing';
@ -644,50 +657,62 @@ An element with transitions will dispatch the following events in addition to an
## in:_fn_/out:_fn_ ## in:_fn_/out:_fn_
```svelte ```svelte
<!--- copy: false --->
in:fn in:fn
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn={params} in:fn={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn|global in:fn|global
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn|global={params} in:fn|global={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn|local in:fn|local
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fn|local={params} in:fn|local={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn out:fn
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn={params} out:fn={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn|global out:fn|global
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn|global={params} out:fn|global={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn|local out:fn|local
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fn|local={params} out:fn|local={params}
``` ```
@ -704,14 +729,17 @@ Unlike with `transition:`, transitions applied with `in:` and `out:` are not bid
## animate:_fn_ ## animate:_fn_
```svelte ```svelte
<!--- copy: false --->
animate:name animate:name
``` ```
```svelte ```svelte
<!--- copy: false --->
animate:name={params} animate:name={params}
``` ```
```js ```js
/// copy: false
// @noErrors // @noErrors
animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => { animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {
delay?: number, delay?: number,
@ -723,6 +751,7 @@ animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) =>
``` ```
```ts ```ts
/// copy: false
// @noErrors // @noErrors
DOMRect { DOMRect {
bottom: number, bottom: number,
@ -772,7 +801,6 @@ The function is called repeatedly _before_ the animation begins, with different
<!-- TODO: Types --> <!-- TODO: Types -->
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';
@ -806,7 +834,6 @@ A custom animation function can also return a `tick` function, which is called _
> If it's possible to use `css` instead of `tick`, do so — CSS animations can run off the main thread, preventing jank on slower devices. > If it's possible to use `css` instead of `tick`, do so — CSS animations can run off the main thread, preventing jank on slower devices.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing';

@ -5,28 +5,24 @@ title: Component directives
## on:_eventname_ ## on:_eventname_
```svelte ```svelte
<!--- copy: false --->
on:eventname={handler} on:eventname={handler}
``` ```
Components can emit events using [`createEventDispatcher`](/docs/svelte#createeventdispatcher) or by forwarding DOM events. Components can emit events using [`createEventDispatcher`](/docs/svelte#createeventdispatcher) or by forwarding DOM events.
```svelte ```svelte
<!-- SomeComponent.svelte -->
<script> <script>
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
</script> </script>
<!-- programmatic dispatching --> <!-- programmatic dispatching -->
<button on:click={() => dispatch('hello')}> <button on:click={() => dispatch('hello')}> one </button>
one
</button>
<!-- declarative event forwarding --> <!-- declarative event forwarding -->
<button on:click> <button on:click> two </button>
two
</button>
``` ```
Listening for component events looks the same as listening for DOM events: Listening for component events looks the same as listening for DOM events:
@ -44,6 +40,7 @@ As with DOM events, if the `on:` directive is used without a value, the event wi
## --style-props ## --style-props
```svelte ```svelte
<!--- copy: false --->
--style-props="anycssvalue" --style-props="anycssvalue"
``` ```
@ -78,7 +75,6 @@ For SVG namespace, the example above desugars into using `<g>` instead:
Svelte's CSS Variables support allows for easily themeable components: Svelte's CSS Variables support allows for easily themeable components:
```svelte ```svelte
<!-- Slider.svelte -->
<style> <style>
.potato-slider-rail { .potato-slider-rail {
background-color: var(--rail-color, var(--theme-color, 'purple')); background-color: var(--rail-color, var(--theme-color, 'purple'));
@ -118,6 +114,7 @@ While Svelte props are reactive without binding, that reactivity only flows down
## bind:this ## bind:this
```svelte ```svelte
<!--- copy: false --->
bind:this={component_instance} bind:this={component_instance}
``` ```

@ -198,7 +198,6 @@ If `this` is the name of a [void element](https://developer.mozilla.org/en-US/do
<script> <script>
let tag = 'div'; let tag = 'div';
/** @type {(e: MouseEvent) => void} */
export let handler; export let handler;
</script> </script>

@ -61,9 +61,7 @@ Note that the value of a `writable` is lost when it is destroyed, for example wh
Creates a store whose value cannot be set from 'outside', the first argument is the store's initial value, and the second argument to `readable` is the same as the second argument to `writable`. Creates a store whose value cannot be set from 'outside', the first argument is the store's initial value, and the second argument to `readable` is the same as the second argument to `writable`.
```js ```ts
<!--- file: App.svelte --->
// ---cut---
import { readable } from 'svelte/store'; import { readable } from 'svelte/store';
const time = readable(new Date(), (set) => { const time = readable(new Date(), (set) => {
@ -114,7 +112,7 @@ The callback can set a value asynchronously by accepting a second argument, `set
In this case, you can also pass a third argument to `derived` — the initial value of the derived store before `set` or `update` is first called. If no initial value is specified, the store's initial value will be `undefined`. In this case, you can also pass a third argument to `derived` — the initial value of the derived store before `set` or `update` is first called. If no initial value is specified, the store's initial value will be `undefined`.
```js ```ts
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { type Writable } from 'svelte/store'; import { type Writable } from 'svelte/store';
@ -129,13 +127,17 @@ export {};
// ---cut--- // ---cut---
import { derived } from 'svelte/store'; import { derived } from 'svelte/store';
const delayed = derived(a, ($a, set) => { const delayed = derived(
setTimeout(() => set($a), 1000); a,
}, 2000); ($a, set) => {
setTimeout(() => set($a), 1000);
},
2000
);
const delayedIncrement = derived(a, ($a, set, update) => { const delayedIncrement = derived(a, ($a, set, update) => {
set($a); set($a);
setTimeout(() => update(x => x + 1), 1000); setTimeout(() => update((x) => x + 1), 1000);
// every time $a produces a value, this produces two // every time $a produces a value, this produces two
// values, $a immediately and then $a + 1 a second later // values, $a immediately and then $a + 1 a second later
}); });
@ -143,7 +145,7 @@ const delayedIncrement = derived(a, ($a, set, update) => {
If you return a function from the callback, it will be called when a) the callback runs again, or b) the last subscriber unsubscribes. If you return a function from the callback, it will be called when a) the callback runs again, or b) the last subscriber unsubscribes.
```js ```ts
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { type Writable } from 'svelte/store'; import { type Writable } from 'svelte/store';
@ -224,7 +226,7 @@ Generally, you should read the value of a store by subscribing to it and using t
> This works by creating a subscription, reading the value, then unsubscribing. It's therefore not recommended in hot code paths. > This works by creating a subscription, reading the value, then unsubscribing. It's therefore not recommended in hot code paths.
```js ```ts
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { type Writable } from 'svelte/store'; import { type Writable } from 'svelte/store';

@ -9,14 +9,17 @@ The `svelte/transition` module exports seven functions: `fade`, `blur`, `fly`, `
> EXPORT_SNIPPET: svelte/transition#fade > EXPORT_SNIPPET: svelte/transition#fade
```svelte ```svelte
<!--- copy: false --->
transition:fade={params} transition:fade={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fade={params} in:fade={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fade={params} out:fade={params}
``` ```
@ -45,14 +48,17 @@ You can see the `fade` transition in action in the [transition tutorial](https:/
> EXPORT_SNIPPET: svelte/transition#blur > EXPORT_SNIPPET: svelte/transition#blur
```svelte ```svelte
<!--- copy: false --->
transition:blur={params} transition:blur={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:blur={params} in:blur={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:blur={params} out:blur={params}
``` ```
@ -81,14 +87,17 @@ Animates a `blur` filter alongside an element's opacity.
> EXPORT_SNIPPET: svelte/transition#fly > EXPORT_SNIPPET: svelte/transition#fly
```svelte ```svelte
<!--- copy: false --->
transition:fly={params} transition:fly={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:fly={params} in:fly={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:fly={params} out:fly={params}
``` ```
@ -126,14 +135,17 @@ You can see the `fly` transition in action in the [transition tutorial](https://
> EXPORT_SNIPPET: svelte/transition#slide > EXPORT_SNIPPET: svelte/transition#slide
```svelte ```svelte
<!--- copy: false --->
transition:slide={params} transition:slide={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:slide={params} in:slide={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:slide={params} out:slide={params}
``` ```
@ -165,14 +177,17 @@ Slides an element in and out.
> EXPORT_SNIPPET: svelte/transition#scale > EXPORT_SNIPPET: svelte/transition#scale
```svelte ```svelte
<!--- copy: false --->
transition:scale={params} transition:scale={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:scale={params} in:scale={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:scale={params} out:scale={params}
``` ```
@ -204,14 +219,17 @@ Animates the opacity and scale of an element. `in` transitions animate from an e
> EXPORT_SNIPPET: svelte/transition#draw > EXPORT_SNIPPET: svelte/transition#draw
```svelte ```svelte
<!--- copy: false --->
transition:draw={params} transition:draw={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
in:draw={params} in:draw={params}
``` ```
```svelte ```svelte
<!--- copy: false --->
out:draw={params} out:draw={params}
``` ```

@ -9,6 +9,7 @@ The `svelte/animate` module exports one function for use with Svelte [animations
> EXPORT_SNIPPET: svelte/animate#flip > EXPORT_SNIPPET: svelte/animate#flip
```svelte ```svelte
<!--- copy: false --->
animate:flip={params} animate:flip={params}
``` ```

@ -5,7 +5,6 @@ title: svelte/action
Actions are functions that are called when an element is created. They can return an object with a `destroy` method that is called after the element is unmounted: Actions are functions that are called when an element is created. They can return an object with a `destroy` method that is called after the element is unmounted:
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
/** @type {import('svelte/action').Action} */ /** @type {import('svelte/action').Action} */
function foo(node) { function foo(node) {
@ -27,7 +26,6 @@ An action can have a parameter. If the returned value has an `update` method, it
> Don't worry that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition. > Don't worry that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition.
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
/** @type {string} */ /** @type {string} */
export let bar; export let bar;
@ -56,7 +54,6 @@ An action can have a parameter. If the returned value has an `update` method, it
Sometimes actions emit custom events and apply custom attributes to the element they are applied to. To support this, actions typed with `Action` or `ActionReturn` type can have a last parameter, `Attributes`: Sometimes actions emit custom events and apply custom attributes to the element they are applied to. To support this, actions typed with `Action` or `ActionReturn` type can have a last parameter, `Attributes`:
```svelte ```svelte
<!--- file: App.svelte --->
<script> <script>
/** /**
* @type {import('svelte/action').Action<HTMLDivElement, { prop: any }, { 'on:emit': (e: CustomEvent<string>) => void }>} * @type {import('svelte/action').Action<HTMLDivElement, { prop: any }, { 'on:emit': (e: CustomEvent<string>) => void }>}

@ -90,7 +90,7 @@ Each `markup`, `script` or `style` function must return an object (or a Promise
> Preprocessor functions should return a `map` object whenever possible or else debugging becomes harder as stack traces can't link to the original code correctly. > Preprocessor functions should return a `map` object whenever possible or else debugging becomes harder as stack traces can't link to the original code correctly.
```js ```ts
// @filename: ambient.d.ts // @filename: ambient.d.ts
declare global { declare global {
var source: string; var source: string;
@ -128,6 +128,7 @@ const { code } = await preprocess(
If a `dependencies` array is returned, it will be included in the result object. This is used by packages like [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte) and [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) to watch additional files for changes, in the case where your `<style>` tag has an `@import` (for example). If a `dependencies` array is returned, it will be included in the result object. This is used by packages like [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte) and [rollup-plugin-svelte](https://github.com/sveltejs/rollup-plugin-svelte) to watch additional files for changes, in the case where your `<style>` tag has an `@import` (for example).
```ts ```ts
/// file: preprocess-sass.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
declare global { declare global {
var source: string; var source: string;
@ -204,6 +205,7 @@ Multiple preprocessors can be used together. The output of the first becomes the
> In Svelte 3, all `markup` functions ran first, then all `script` and then all `style` preprocessors. This order was changed in Svelte 4. > In Svelte 3, all `markup` functions ran first, then all `script` and then all `style` preprocessors. This order was changed in Svelte 4.
```js ```js
/// file: multiple-preprocessor.js
// @errors: 2322 // @errors: 2322
// @filename: ambient.d.ts // @filename: ambient.d.ts
declare global { declare global {
@ -255,6 +257,7 @@ The `walk` function provides a way to walk the abstract syntax trees generated b
The walker takes an abstract syntax tree to walk and an object with two optional methods: `enter` and `leave`. For each node, `enter` is called (if present). Then, unless `this.skip()` is called during `enter`, each of the children are traversed, and then `leave` is called on the node. The walker takes an abstract syntax tree to walk and an object with two optional methods: `enter` and `leave`. For each node, `enter` is called (if present). Then, unless `this.skip()` is called during `enter`, each of the children are traversed, and then `leave` is called on the node.
```js ```js
/// file: compiler-walk.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
declare global { declare global {
var ast: import('estree').Node; var ast: import('estree').Node;

@ -67,6 +67,7 @@ Whereas children of `target` are normally left alone, `hydrate: true` will cause
The existing DOM doesn't need to match the component — Svelte will 'repair' the DOM as it goes. The existing DOM doesn't need to match the component — Svelte will 'repair' the DOM as it goes.
```ts ```ts
/// file: index.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte'; import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
@ -150,6 +151,7 @@ Causes the `callback` function to be called whenever the component dispatches an
A function is returned that will remove the event listener when called. A function is returned that will remove the event listener when called.
```ts ```ts
/// file: index.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte'; import { SvelteComponent, ComponentConstructorOptions } from 'svelte';
@ -231,6 +233,7 @@ If a component is compiled with `accessors: true`, each instance will have gette
By default, `accessors` is `false`, unless you're compiling as a custom element. By default, `accessors` is `false`, unless you're compiling as a custom element.
```js ```js
/// file: index.js
// @filename: ambient.d.ts // @filename: ambient.d.ts
import { SvelteComponent, ComponentConstructorOptions } from 'svelte'; import { SvelteComponent, ComponentConstructorOptions } from 'svelte';

@ -59,42 +59,13 @@ It will show up on hover.
Note: The `@component` is necessary in the HTML comment which describes your component. Note: The `@component` is necessary in the HTML comment which describes your component.
## What about TypeScript support?
You need to install a preprocessor such as [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess). You can run type checking from the command line with [svelte-check](https://www.npmjs.com/package/svelte-check).
To declare the type of a reactive variable in a Svelte template, you should use the following syntax:
```ts
const count: number = 100;
// ---cut---
let x: number;
$: x = count + 1;
```
To import a type or interface make sure to use [TypeScript's `type` modifier](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export):
```ts
// @filename: SomeFile.ts
export interface SomeInterface {
foo: string;
}
// @filename: index.ts
// ---cut---
import type { SomeInterface } from './SomeFile';
```
You must use the `type` modifier because `svelte-preprocess` doesn't know whether an import is a type or a value — it only transpiles one file at a time without knowledge of the other files and therefore can't safely erase imports which only contain types without this modifier present.
## Does Svelte scale? ## Does Svelte scale?
There will be a blog post about this eventually, but in the meantime, check out [this issue](https://github.com/sveltejs/svelte/issues/2546). There will be a blog post about this eventually, but in the meantime, check out [this issue](https://github.com/sveltejs/svelte/issues/2546).
## Is there a UI component library? ## Is there a UI component library?
There are several UI component libraries as well as standalone components. Find them under the [components section](https://sveltesociety.dev/components#design-systems) of the Svelte Society website. There are several UI component libraries as well as standalone components. Find them under the [design systems section of the components page](https://sveltesociety.dev/components#design-systems) on the Svelte Society website.
## How do I test Svelte apps? ## How do I test Svelte apps?
@ -131,6 +102,17 @@ If you need hash-based routing on the client side, check out [svelte-spa-router]
You can see a [community-maintained list of routers on sveltesociety.dev](https://sveltesociety.dev/components#routers). You can see a [community-maintained list of routers on sveltesociety.dev](https://sveltesociety.dev/components#routers).
## Can I tell Svelte not to remove my unused styles?
No. Svelte removes the styles from the component and warns you about them in order to prevent issues that would otherwise arise.
Svelte's component style scoping works by generating a class unique to the given component, adding it to the relevant elements in the component that are under Svelte's control, and then adding it to each of the selectors in that component's styles. When the compiler can't see what elements a style selector applies to, there would be two bad options for keeping it:
- If it keeps the selector and adds the scoping class to it, the selector will likely not match the expected elements in the component, and they definitely won't if they were created by a child component or `{@html ...}`.
- If it keeps the selector without adding the scoping class to it, the given style will become a global style, affecting your entire page.
If you need to style something that Svelte can't identify at compile time, you will need to explicitly opt into global styles by using `:global(...)`. But also keep in mind that you can wrap `:global(...)` around only part of a selector. `.foo :global(.bar) { ... }` will style any `.bar` elements that appear within the component's `.foo` elements. As long as there's some parent element in the current component to start from, partially global selectors like this will almost always be able to get you what you want.
## Is Svelte v2 still available? ## Is Svelte v2 still available?
New features aren't being added to it, and bugs will probably only be fixed if they are extremely nasty or present some sort of security vulnerability. New features aren't being added to it, and bugs will probably only be fixed if they are extremely nasty or present some sort of security vulnerability.

@ -144,17 +144,17 @@ Since Svelte version 4.2 / `svelte-check` version 3.5 / VS Code extension versio
```ts ```ts
/// file: additional-svelte-typings.d.ts /// file: additional-svelte-typings.d.ts
import { HTMLButtonAttributes } from 'svelte/elements' import { HTMLButtonAttributes } from 'svelte/elements';
declare module 'svelte/elements' { declare module 'svelte/elements' {
export interface SvelteHTMLElements { export interface SvelteHTMLElements {
'custom-button': HTMLButtonAttributes; 'custom-button': HTMLButtonAttributes;
} }
// allows for more granular control over what element to add the typings to // allows for more granular control over what element to add the typings to
export interface HTMLButtonAttributes { export interface HTMLButtonAttributes {
'veryexperimentalattribute'?: string; veryexperimentalattribute?: string;
} }
} }
export {}; // ensure this is not an ambient module, else types will be overridden instead of augmented export {}; // ensure this is not an ambient module, else types will be overridden instead of augmented

@ -1,5 +1,27 @@
# svelte # svelte
## 4.2.1
### Patch Changes
- fix: update style directive when style attribute is present and is updated via an object prop ([#9187](https://github.com/sveltejs/svelte/pull/9187))
- fix: css sourcemap generation with unicode filenames ([#9120](https://github.com/sveltejs/svelte/pull/9120))
- fix: do not add module declared variables as dependencies ([#9122](https://github.com/sveltejs/svelte/pull/9122))
- fix: handle `svelte:element` with dynamic this and spread attributes ([#9112](https://github.com/sveltejs/svelte/pull/9112))
- fix: silence false positive reactive component warning ([#9094](https://github.com/sveltejs/svelte/pull/9094))
- fix: head duplication when binding is present ([#9124](https://github.com/sveltejs/svelte/pull/9124))
- fix: take custom attribute name into account when reflecting property ([#9140](https://github.com/sveltejs/svelte/pull/9140))
- fix: add `indeterminate` to the list of HTMLAttributes ([#9180](https://github.com/sveltejs/svelte/pull/9180))
- fix: recognize option value on spread attribute ([#9125](https://github.com/sveltejs/svelte/pull/9125))
## 4.2.0 ## 4.2.0
### Minor Changes ### Minor Changes

@ -808,6 +808,7 @@ export interface HTMLInputAttributes extends HTMLAttributes<HTMLInputElement> {
formnovalidate?: boolean | undefined | null; formnovalidate?: boolean | undefined | null;
formtarget?: string | undefined | null; formtarget?: string | undefined | null;
height?: number | string | undefined | null; height?: number | string | undefined | null;
indeterminate?: boolean | undefined | null;
list?: string | undefined | null; list?: string | undefined | null;
max?: number | string | undefined | null; max?: number | string | undefined | null;
maxlength?: number | undefined | null; maxlength?: number | undefined | null;

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "4.2.0", "version": "4.2.1",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"type": "module", "type": "module",
"module": "src/runtime/index.js", "module": "src/runtime/index.js",
@ -83,7 +83,7 @@
"posttest": "agadoo src/internal/index.js", "posttest": "agadoo src/internal/index.js",
"prepublishOnly": "pnpm build", "prepublishOnly": "pnpm build",
"types": "node ./scripts/generate-dts.js", "types": "node ./scripts/generate-dts.js",
"lint": "prettier . --cache --plugin-search-dir=. --check && eslint \"{src,test}/**/*.{ts,js}\" --cache" "lint": "prettier . --cache --plugin-search-dir=. --check && eslint \"{scripts,src,test}/**/*.js\" --cache --fix"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -129,6 +129,7 @@
"agadoo": "^3.0.0", "agadoo": "^3.0.0",
"dts-buddy": "^0.1.7", "dts-buddy": "^0.1.7",
"esbuild": "^0.18.11", "esbuild": "^0.18.11",
"eslint-plugin-lube": "^0.1.7",
"happy-dom": "^9.20.3", "happy-dom": "^9.20.3",
"jsdom": "^21.1.2", "jsdom": "^21.1.2",
"kleur": "^4.1.5", "kleur": "^4.1.5",

@ -0,0 +1,6 @@
{
"plugins": ["lube"],
"rules": {
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
}
}

@ -1,8 +1,8 @@
// Compile all Svelte files in a directory to JS and CSS files // Compile all Svelte files in a directory to JS and CSS files
// Usage: node scripts/compile-test.js <directory> // Usage: node scripts/compile-test.js <directory>
import { mkdirSync, readFileSync, writeFileSync } from 'fs'; import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import path from 'path'; import path from 'node:path';
import glob from 'tiny-glob/sync.js'; import glob from 'tiny-glob/sync.js';
import { compile } from '../src/compiler/index.js'; import { compile } from '../src/compiler/index.js';

@ -1,18 +1,18 @@
import * as fs from 'fs'; import * as fs from 'node:fs';
import { createBundle } from 'dts-buddy'; import { createBundle } from 'dts-buddy';
// It may look weird, but the imports MUST be ending with index.js to be properly resolved in all TS modes // It may look weird, but the imports MUST be ending with index.js to be properly resolved in all TS modes
for (const name of ['action', 'animate', 'easing', 'motion', 'store', 'transition']) { for (const name of ['action', 'animate', 'easing', 'motion', 'store', 'transition']) {
fs.writeFileSync(`${name}.d.ts`, `import './types/index.js';`); fs.writeFileSync(`${name}.d.ts`, "import './types/index.js';");
} }
fs.writeFileSync('index.d.ts', `import './types/index.js';`); fs.writeFileSync('index.d.ts', "import './types/index.js';");
fs.writeFileSync('compiler.d.ts', `import './types/index.js';`); fs.writeFileSync('compiler.d.ts', "import './types/index.js';");
// TODO: some way to mark these as deprecated // TODO: some way to mark these as deprecated
fs.mkdirSync('./types/compiler', { recursive: true }); fs.mkdirSync('./types/compiler', { recursive: true });
fs.writeFileSync('./types/compiler/preprocess.d.ts', `import '../index.js';`); fs.writeFileSync('./types/compiler/preprocess.d.ts', "import '../index.js';");
fs.writeFileSync('./types/compiler/interfaces.d.ts', `import '../index.js';`); fs.writeFileSync('./types/compiler/interfaces.d.ts', "import '../index.js';");
await createBundle({ await createBundle({
output: 'types/index.d.ts', output: 'types/index.d.ts',

@ -6,8 +6,8 @@ Please run `node scripts/globals-extractor.js` at the project root.
see: https://github.com/microsoft/TypeScript/tree/main/lib see: https://github.com/microsoft/TypeScript/tree/main/lib
---------------------------------------------------------------------- */ ---------------------------------------------------------------------- */
import http from 'https'; import http from 'node:https';
import fs from 'fs'; import fs from 'node:fs';
const GLOBAL_TS_PATH = './src/compiler/utils/globals.js'; const GLOBAL_TS_PATH = './src/compiler/utils/globals.js';

@ -0,0 +1,6 @@
{
"plugins": ["lube"],
"rules": {
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
}
}

@ -1555,8 +1555,8 @@ export default class Component {
}, []) }, [])
); );
if (cycle && cycle.length) { if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]); const declaration_list = lookup.get(cycle[0]);
const declaration = declarationList[0]; const declaration = declaration_list[0];
return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle)); return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle));
} }

@ -4,7 +4,7 @@ import { b } from 'code-red';
* @param {any} program * @param {any} program
* @param {import('estree').Identifier} name * @param {import('estree').Identifier} name
* @param {string} banner * @param {string} banner
* @param {any} sveltePath * @param {any} svelte_path
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers * @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals * @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
* @param {import('estree').ImportDeclaration[]} imports * @param {import('estree').ImportDeclaration[]} imports
@ -15,21 +15,21 @@ export default function create_module(
program, program,
name, name,
banner, banner,
sveltePath = 'svelte', svelte_path = 'svelte',
helpers, helpers,
globals, globals,
imports, imports,
module_exports, module_exports,
exports_from exports_from
) { ) {
const internal_path = `${sveltePath}/internal`; const internal_path = `${svelte_path}/internal`;
helpers.sort((a, b) => (a.name < b.name ? -1 : 1)); helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
globals.sort((a, b) => (a.name < b.name ? -1 : 1)); globals.sort((a, b) => (a.name < b.name ? -1 : 1));
return esm( return esm(
program, program,
name, name,
banner, banner,
sveltePath, svelte_path,
internal_path, internal_path,
helpers, helpers,
globals, globals,
@ -41,11 +41,11 @@ export default function create_module(
/** /**
* @param {any} source * @param {any} source
* @param {any} sveltePath * @param {any} svelte_path
*/ */
function edit_source(source, sveltePath) { function edit_source(source, svelte_path) {
return source === 'svelte' || source.startsWith('svelte/') return source === 'svelte' || source.startsWith('svelte/')
? source.replace('svelte', sveltePath) ? source.replace('svelte', svelte_path)
: source; : source;
} }
@ -84,7 +84,7 @@ function get_internal_globals(globals, helpers) {
* @param {any} program * @param {any} program
* @param {import('estree').Identifier} name * @param {import('estree').Identifier} name
* @param {string} banner * @param {string} banner
* @param {string} sveltePath * @param {string} svelte_path
* @param {string} internal_path * @param {string} internal_path
* @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers * @param {Array<{ name: string; alias: import('estree').Identifier }>} helpers
* @param {Array<{ name: string; alias: import('estree').Identifier }>} globals * @param {Array<{ name: string; alias: import('estree').Identifier }>} globals
@ -96,7 +96,7 @@ function esm(
program, program,
name, name,
banner, banner,
sveltePath, svelte_path,
internal_path, internal_path,
helpers, helpers,
globals, globals,
@ -118,7 +118,7 @@ function esm(
/** @param {any} node */ /** @param {any} node */
function rewrite_import(node) { function rewrite_import(node) {
const value = edit_source(node.source.value, sveltePath); const value = edit_source(node.source.value, svelte_path);
if (node.source.value !== value) { if (node.source.value !== value) {
node.source.value = value; node.source.value = value;
node.source.raw = null; node.source.raw = null;

@ -98,7 +98,7 @@ export default class Binding extends Node {
this.is_readonly = this.is_readonly =
regex_dimensions.test(this.name) || regex_dimensions.test(this.name) ||
regex_box_size.test(this.name) || regex_box_size.test(this.name) ||
(isElement(parent) && (is_element(parent) &&
((parent.is_media_node() && read_only_media_attributes.has(this.name)) || ((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file'))) /* TODO others? */; (parent.name === 'input' && type === 'file'))) /* TODO others? */;
} }
@ -127,6 +127,6 @@ export default class Binding extends Node {
* @param {import('./shared/Node.js').default} node * @param {import('./shared/Node.js').default} node
* @returns {node is import('./Element.js').default} * @returns {node is import('./Element.js').default}
*/ */
function isElement(node) { function is_element(node) {
return !!(/** @type {any} */ (node).is_media_node); return !!(/** @type {any} */ (node).is_media_node);
} }

@ -84,7 +84,9 @@ export default class EachBlock extends AbstractBlock {
this.has_animation = false; this.has_animation = false;
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
if (this.has_animation) { if (this.has_animation) {
this.children = this.children.filter((child) => !isEmptyNode(child) && !isCommentNode(child)); this.children = this.children.filter(
(child) => !is_empty_node(child) && !is_comment_node(child)
);
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find( const child = this.children.find(
(child) => !!(/** @type {import('./Element.js').default} */ (child).animation) (child) => !!(/** @type {import('./Element.js').default} */ (child).animation)
@ -102,11 +104,11 @@ export default class EachBlock extends AbstractBlock {
} }
/** @param {import('./interfaces.js').INode} node */ /** @param {import('./interfaces.js').INode} node */
function isEmptyNode(node) { function is_empty_node(node) {
return node.type === 'Text' && node.data.trim() === ''; return node.type === 'Text' && node.data.trim() === '';
} }
/** @param {import('./interfaces.js').INode} node */ /** @param {import('./interfaces.js').INode} node */
function isCommentNode(node) { function is_comment_node(node) {
return node.type === 'Comment'; return node.type === 'Comment';
} }

@ -1,5 +1,6 @@
import { is_html, is_svg, is_void } from '../../../shared/utils/names.js'; import { is_html, is_svg, is_void } from '../../../shared/utils/names.js';
import Node from './shared/Node.js'; import Node from './shared/Node.js';
import { walk } from 'estree-walker';
import Attribute from './Attribute.js'; import Attribute from './Attribute.js';
import Binding from './Binding.js'; import Binding from './Binding.js';
import EventHandler from './EventHandler.js'; import EventHandler from './EventHandler.js';
@ -430,7 +431,7 @@ export default class Element extends Node {
} }
if (this.name === 'textarea') { if (this.name === 'textarea') {
if (info.children.length > 0) { if (info.children.length > 0) {
const value_attribute = info.attributes.find((node) => node.name === 'value'); const value_attribute = get_value_attribute(info.attributes);
if (value_attribute) { if (value_attribute) {
component.error(value_attribute, compiler_errors.textarea_duplicate_value); component.error(value_attribute, compiler_errors.textarea_duplicate_value);
return; return;
@ -449,7 +450,7 @@ export default class Element extends Node {
// Special case — treat these the same way: // Special case — treat these the same way:
// <option>{foo}</option> // <option>{foo}</option>
// <option value={foo}>{foo}</option> // <option value={foo}>{foo}</option>
const value_attribute = info.attributes.find((attribute) => attribute.name === 'value'); const value_attribute = get_value_attribute(info.attributes);
if (!value_attribute) { if (!value_attribute) {
info.attributes.push({ info.attributes.push({
type: 'Attribute', type: 'Attribute',
@ -875,7 +876,7 @@ export default class Element extends Node {
) { ) {
const interactive_handlers = handlers const interactive_handlers = handlers
.map((handler) => handler.name) .map((handler) => handler.name)
.filter((handlerName) => a11y_interactive_handlers.has(handlerName)); .filter((handler_name) => a11y_interactive_handlers.has(handler_name));
if (interactive_handlers.length > 0) { if (interactive_handlers.length > 0) {
component.warn( component.warn(
this, this,
@ -1420,3 +1421,30 @@ function within_custom_element(parent) {
} }
return false; return false;
} }
/**
* @param {any[]} attributes
*/
function get_value_attribute(attributes) {
let node_value;
attributes.forEach((node) => {
if (node.type !== 'Spread' && node.name.toLowerCase() === 'value') {
node_value = node;
}
if (node.type === 'Spread') {
walk(/** @type {any} */ (node.expression), {
enter(/** @type {import('estree').Node} */ node) {
if (node_value) {
this.skip();
}
if (node.type === 'Identifier') {
if (/** @type {import('estree').Identifier} */ (node).name.toLowerCase() === 'value') {
node_value = node;
}
}
}
});
}
});
return node_value;
}

@ -73,8 +73,8 @@ function sort_consts_nodes(consts_nodes, component) {
}, []) }, [])
); );
if (cycle && cycle.length) { if (cycle && cycle.length) {
const nodeList = lookup.get(cycle[0]); const node_list = lookup.get(cycle[0]);
const node = nodeList[0]; const node = node_list[0];
component.error(node.node, compiler_errors.cyclical_const_tags(cycle)); component.error(node.node, compiler_errors.cyclical_const_tags(cycle));
} }

@ -990,7 +990,8 @@ export default class ElementWrapper extends Wrapper {
const static_attributes = []; const static_attributes = [];
this.attributes.forEach((attr) => { this.attributes.forEach((attr) => {
if (attr instanceof SpreadAttributeWrapper) { if (attr instanceof SpreadAttributeWrapper) {
static_attributes.push({ type: 'SpreadElement', argument: attr.node.expression.node }); const snippet = { type: 'SpreadElement', argument: attr.node.expression.manipulate(block) };
static_attributes.push(snippet);
} else { } else {
const name = attr.property_name || attr.name; const name = attr.property_name || attr.name;
static_attributes.push(p`${name}: ${attr.get_value(block)}`); static_attributes.push(p`${name}: ${attr.get_value(block)}`);
@ -1240,11 +1241,7 @@ export default class ElementWrapper extends Wrapper {
} }
if (this.dynamic_style_dependencies.size > 0) { if (this.dynamic_style_dependencies.size > 0) {
maybe_create_style_changed_var(); maybe_create_style_changed_var();
// If all dependencies are same as the style attribute dependencies, then we can skip the dirty check condition = x`${condition} || ${style_changed_var}`;
condition =
all_deps.size === this.dynamic_style_dependencies.size
? style_changed_var
: x`${style_changed_var} || ${condition}`;
} }
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {

@ -105,14 +105,19 @@ export default class InlineComponentWrapper extends Wrapper {
this.slots.set(name, slot_definition); this.slots.set(name, slot_definition);
} }
warn_if_reactive() { warn_if_reactive() {
const { name } = this.node; let { name } = this.node;
const variable = this.renderer.component.var_lookup.get(name); const top = name.split('.')[0]; // <T.foo/> etc. should check for T instead of "T.foo"
const variable = this.renderer.component.var_lookup.get(top);
if (!variable) { if (!variable) {
return; return;
} }
const ignores = extract_ignores_above_node(this.node); const ignores = extract_ignores_above_node(this.node);
this.renderer.component.push_ignores(ignores); this.renderer.component.push_ignores(ignores);
if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) { if (
variable.reassigned ||
variable.export_name || // or a prop
variable.mutated
) {
this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name)); this.renderer.component.warn(this.node, compiler_warnings.reactive_component(name));
} }
this.renderer.component.pop_ignores(); this.renderer.component.pop_ignores();

@ -94,14 +94,14 @@ export default class WindowWrapper extends Wrapper {
bindings.scrollX && bindings.scrollY bindings.scrollX && bindings.scrollY
? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state` ? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`; : x`"${bindings.scrollX || bindings.scrollY}" in this._state`;
const scrollX = bindings.scrollX && x`this._state.${bindings.scrollX}`; const scroll_x = bindings.scrollX && x`this._state.${bindings.scrollX}`;
const scrollY = bindings.scrollY && x`this._state.${bindings.scrollY}`; const scroll_y = bindings.scrollY && x`this._state.${bindings.scrollY}`;
renderer.meta_bindings.push(b` renderer.meta_bindings.push(b`
if (${condition}) { if (${condition}) {
@_scrollTo(${scrollX || '@_window.pageXOffset'}, ${scrollY || '@_window.pageYOffset'}); @_scrollTo(${scroll_x || '@_window.pageXOffset'}, ${scroll_y || '@_window.pageYOffset'});
} }
${scrollX && `${scrollX} = @_window.pageXOffset;`} ${scroll_x && `${scroll_x} = @_window.pageXOffset;`}
${scrollY && `${scrollY} = @_window.pageYOffset;`} ${scroll_y && `${scroll_y} = @_window.pageYOffset;`}
`); `);
block.event_listeners.push(x` block.event_listeners.push(x`
@listen(@_window, "${event}", () => { @listen(@_window, "${event}", () => {
@ -132,17 +132,17 @@ export default class WindowWrapper extends Wrapper {
// special case... might need to abstract this out if we add more special cases // special case... might need to abstract this out if we add more special cases
if (bindings.scrollX || bindings.scrollY) { if (bindings.scrollX || bindings.scrollY) {
const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean)); const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean));
const scrollX = bindings.scrollX const scroll_x = bindings.scrollX
? renderer.reference(bindings.scrollX) ? renderer.reference(bindings.scrollX)
: x`@_window.pageXOffset`; : x`@_window.pageXOffset`;
const scrollY = bindings.scrollY const scroll_y = bindings.scrollY
? renderer.reference(bindings.scrollY) ? renderer.reference(bindings.scrollY)
: x`@_window.pageYOffset`; : x`@_window.pageYOffset`;
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition} && !${scrolling}) { if (${condition} && !${scrolling}) {
${scrolling} = true; ${scrolling} = true;
@_clearTimeout(${scrolling_timeout}); @_clearTimeout(${scrolling_timeout});
@_scrollTo(${scrollX}, ${scrollY}); @_scrollTo(${scroll_x}, ${scroll_y});
${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100); ${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100);
} }
`); `);

@ -3,8 +3,11 @@ import { is_reserved_keyword } from '../../../utils/reserved_keywords.js';
/** @param {import('../../../../interfaces.js').Var} variable */ /** @param {import('../../../../interfaces.js').Var} variable */
export default function is_dynamic(variable) { export default function is_dynamic(variable) {
if (variable) { if (variable) {
if (variable.mutated || variable.reassigned) return true; // dynamic internal state // Only variables declared in the instance script tags should be considered dynamic
if (!variable.module && variable.writable && variable.export_name) return true; // writable props const is_declared_in_reactive_context = !variable.module && !variable.global;
if (is_declared_in_reactive_context && (variable.mutated || variable.reassigned)) return true; // dynamic internal state
if (is_declared_in_reactive_context && variable.writable && variable.export_name) return true; // writable props
if (is_reserved_keyword(variable.name)) return true; if (is_reserved_keyword(variable.name)) return true;
} }
return false; return false;

@ -8,7 +8,7 @@ import * as node from './node/index.js';
* *
* The new nodes are located in `./node`. * The new nodes are located in `./node`.
*/ */
const cqSyntax = fork({ const cq_syntax = fork({
atrule: { atrule: {
// extend or override at-rule dictionary // extend or override at-rule dictionary
container: { container: {
@ -16,8 +16,8 @@ const cqSyntax = fork({
prelude() { prelude() {
return this.createSingleNodeList(this.ContainerQuery()); return this.createSingleNodeList(this.ContainerQuery());
}, },
block(isStyleBlock = false) { block(is_style_block = false) {
return this.Block(isStyleBlock); return this.Block(is_style_block);
} }
} }
} }
@ -25,4 +25,4 @@ const cqSyntax = fork({
node node
}); });
export const parse = cqSyntax.parse; export const parse = cq_syntax.parse;

@ -16,7 +16,7 @@ export const structure = {
value: ['Identifier', 'Number', 'Comparison', 'Dimension', 'QueryCSSFunction', 'Ratio', null] value: ['Identifier', 'Number', 'Comparison', 'Dimension', 'QueryCSSFunction', 'Ratio', null]
}; };
function lookup_non_WS_type_and_value(offset, type, referenceStr) { function lookup_non_ws_type_and_value(offset, type, reference_str) {
let current_type; let current_type;
do { do {
@ -26,7 +26,7 @@ function lookup_non_WS_type_and_value(offset, type, referenceStr) {
} }
} while (current_type !== 0); // NULL -> 0 } while (current_type !== 0); // NULL -> 0
return current_type === type ? this.lookupValue(offset - 1, referenceStr) : false; return current_type === type ? this.lookupValue(offset - 1, reference_str) : false;
} }
export function parse() { export function parse() {
@ -40,7 +40,7 @@ export function parse() {
while (!this.eof && this.tokenType !== RightParenthesis) { while (!this.eof && this.tokenType !== RightParenthesis) {
switch (this.tokenType) { switch (this.tokenType) {
case Number: case Number:
if (lookup_non_WS_type_and_value.call(this, 1, Delim, '/')) { if (lookup_non_ws_type_and_value.call(this, 1, Delim, '/')) {
child = this.Ratio(); child = this.Ratio();
} else { } else {
child = this.Number(); child = this.Number();

@ -84,7 +84,17 @@ function make_dirty(component, i) {
component.$$.dirty[(i / 31) | 0] |= 1 << i % 31; component.$$.dirty[(i / 31) | 0] |= 1 << i % 31;
} }
/** @returns {void} */ // TODO: Document the other params
/**
* @param {SvelteComponent} component
* @param {import('./public.js').ComponentConstructorOptions} options
*
* @param {import('./utils.js')['not_equal']} not_equal Used to compare props and state values.
* @param {(target: Element | ShadowRoot) => void} [append_styles] Function that appends styles to the DOM when the component is first initialised.
* This will be the `add_css` function from the compiled component.
*
* @returns {void}
*/
export function init( export function init(
component, component,
options, options,
@ -92,7 +102,7 @@ export function init(
create_fragment, create_fragment,
not_equal, not_equal,
props, props,
append_styles, append_styles = null,
dirty = [-1] dirty = [-1]
) { ) {
const parent_component = current_component; const parent_component = current_component;
@ -139,8 +149,9 @@ export function init(
if (options.target) { if (options.target) {
if (options.hydrate) { if (options.hydrate) {
start_hydrating(); start_hydrating();
// TODO: what is the correct type here?
// @ts-expect-error
const nodes = children(options.target); const nodes = children(options.target);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment && $$.fragment.l(nodes); $$.fragment && $$.fragment.l(nodes);
nodes.forEach(detach); nodes.forEach(detach);
} else { } else {

@ -1,5 +1,7 @@
import { ResizeObserverSingleton } from './ResizeObserverSingleton.js';
import { contenteditable_truthy_values, has_prop } from './utils.js'; import { contenteditable_truthy_values, has_prop } from './utils.js';
import { ResizeObserverSingleton } from './ResizeObserverSingleton.js';
// Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
// at the end of hydration without touching the remaining nodes. // at the end of hydration without touching the remaining nodes.
let is_hydrating = false; let is_hydrating = false;
@ -50,14 +52,14 @@ function init_hydrate(target) {
let children = /** @type {ArrayLike<NodeEx2>} */ (target.childNodes); let children = /** @type {ArrayLike<NodeEx2>} */ (target.childNodes);
// If target is <head>, there may be children without claim_order // If target is <head>, there may be children without claim_order
if (target.nodeName === 'HEAD') { if (target.nodeName === 'HEAD') {
const myChildren = []; const my_children = [];
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
const node = children[i]; const node = children[i];
if (node.claim_order !== undefined) { if (node.claim_order !== undefined) {
myChildren.push(node); my_children.push(node);
} }
} }
children = myChildren; children = my_children;
} }
/* /*
* Reorder claimed children optimally. * Reorder claimed children optimally.
@ -87,15 +89,15 @@ function init_hydrate(target) {
// Find the largest subsequence length such that it ends in a value less than our current value // Find the largest subsequence length such that it ends in a value less than our current value
// upper_bound returns first greater value, so we subtract one // upper_bound returns first greater value, so we subtract one
// with fast path for when we are on the current longest subsequence // with fast path for when we are on the current longest subsequence
const seqLen = const seq_len =
(longest > 0 && children[m[longest]].claim_order <= current (longest > 0 && children[m[longest]].claim_order <= current
? longest + 1 ? longest + 1
: upper_bound(1, longest, (idx) => children[m[idx]].claim_order, current)) - 1; : upper_bound(1, longest, (idx) => children[m[idx]].claim_order, current)) - 1;
p[i] = m[seqLen] + 1; p[i] = m[seq_len] + 1;
const newLen = seqLen + 1; const new_len = seq_len + 1;
// We can guarantee that current is the smallest value. Otherwise, we would have generated a longer sequence. // We can guarantee that current is the smallest value. Otherwise, we would have generated a longer sequence.
m[newLen] = i; m[new_len] = i;
longest = Math.max(newLen, longest); longest = Math.max(new_len, longest);
} }
// The longest increasing subsequence of nodes (initially reversed) // The longest increasing subsequence of nodes (initially reversed)
@ -108,28 +110,28 @@ function init_hydrate(target) {
/** /**
* @type {NodeEx2[]} * @type {NodeEx2[]}
*/ */
const toMove = []; const to_move = [];
let last = children.length - 1; let last = children.length - 1;
for (let cur = m[longest] + 1; cur != 0; cur = p[cur - 1]) { for (let cur = m[longest] + 1; cur != 0; cur = p[cur - 1]) {
lis.push(children[cur - 1]); lis.push(children[cur - 1]);
for (; last >= cur; last--) { for (; last >= cur; last--) {
toMove.push(children[last]); to_move.push(children[last]);
} }
last--; last--;
} }
for (; last >= 0; last--) { for (; last >= 0; last--) {
toMove.push(children[last]); to_move.push(children[last]);
} }
lis.reverse(); lis.reverse();
// We sort the nodes being moved to guarantee that their insertion order matches the claim order // We sort the nodes being moved to guarantee that their insertion order matches the claim order
toMove.sort((a, b) => a.claim_order - b.claim_order); to_move.sort((a, b) => a.claim_order - b.claim_order);
// Finally, we move the nodes // Finally, we move the nodes
for (let i = 0, j = 0; i < toMove.length; i++) { for (let i = 0, j = 0; i < to_move.length; i++) {
while (j < lis.length && toMove[i].claim_order >= lis[j].claim_order) { while (j < lis.length && to_move[i].claim_order >= lis[j].claim_order) {
j++; j++;
} }
const anchor = j < lis.length ? lis[j] : null; const anchor = j < lis.length ? lis[j] : null;
target.insertBefore(toMove[i], anchor); target.insertBefore(to_move[i], anchor);
} }
} }
@ -624,26 +626,26 @@ function init_claim_info(nodes) {
* @template {ChildNodeEx} R * @template {ChildNodeEx} R
* @param {ChildNodeArray} nodes * @param {ChildNodeArray} nodes
* @param {(node: ChildNodeEx) => node is R} predicate * @param {(node: ChildNodeEx) => node is R} predicate
* @param {(node: ChildNodeEx) => ChildNodeEx | undefined} processNode * @param {(node: ChildNodeEx) => ChildNodeEx | undefined} process_node
* @param {() => R} createNode * @param {() => R} create_node
* @param {boolean} dontUpdateLastIndex * @param {boolean} dont_update_last_index
* @returns {R} * @returns {R}
*/ */
function claim_node(nodes, predicate, processNode, createNode, dontUpdateLastIndex = false) { function claim_node(nodes, predicate, process_node, create_node, dont_update_last_index = false) {
// Try to find nodes in an order such that we lengthen the longest increasing subsequence // Try to find nodes in an order such that we lengthen the longest increasing subsequence
init_claim_info(nodes); init_claim_info(nodes);
const resultNode = (() => { const result_node = (() => {
// We first try to find an element after the previous one // We first try to find an element after the previous one
for (let i = nodes.claim_info.last_index; i < nodes.length; i++) { for (let i = nodes.claim_info.last_index; i < nodes.length; i++) {
const node = nodes[i]; const node = nodes[i];
if (predicate(node)) { if (predicate(node)) {
const replacement = processNode(node); const replacement = process_node(node);
if (replacement === undefined) { if (replacement === undefined) {
nodes.splice(i, 1); nodes.splice(i, 1);
} else { } else {
nodes[i] = replacement; nodes[i] = replacement;
} }
if (!dontUpdateLastIndex) { if (!dont_update_last_index) {
nodes.claim_info.last_index = i; nodes.claim_info.last_index = i;
} }
return node; return node;
@ -654,13 +656,13 @@ function claim_node(nodes, predicate, processNode, createNode, dontUpdateLastInd
for (let i = nodes.claim_info.last_index - 1; i >= 0; i--) { for (let i = nodes.claim_info.last_index - 1; i >= 0; i--) {
const node = nodes[i]; const node = nodes[i];
if (predicate(node)) { if (predicate(node)) {
const replacement = processNode(node); const replacement = process_node(node);
if (replacement === undefined) { if (replacement === undefined) {
nodes.splice(i, 1); nodes.splice(i, 1);
} else { } else {
nodes[i] = replacement; nodes[i] = replacement;
} }
if (!dontUpdateLastIndex) { if (!dont_update_last_index) {
nodes.claim_info.last_index = i; nodes.claim_info.last_index = i;
} else if (replacement === undefined) { } else if (replacement === undefined) {
// Since we spliced before the last_index, we decrease it // Since we spliced before the last_index, we decrease it
@ -670,11 +672,11 @@ function claim_node(nodes, predicate, processNode, createNode, dontUpdateLastInd
} }
} }
// If we can't find any matching node, we create a new one // If we can't find any matching node, we create a new one
return createNode(); return create_node();
})(); })();
resultNode.claim_order = nodes.claim_info.total_claimed; result_node.claim_order = nodes.claim_info.total_claimed;
nodes.claim_info.total_claimed += 1; nodes.claim_info.total_claimed += 1;
return resultNode; return result_node;
} }
/** /**
@ -736,13 +738,13 @@ export function claim_text(nodes, data) {
(node) => node.nodeType === 3, (node) => node.nodeType === 3,
/** @param {Text} node */ /** @param {Text} node */
(node) => { (node) => {
const dataStr = '' + data; const data_str = '' + data;
if (node.data.startsWith(dataStr)) { if (node.data.startsWith(data_str)) {
if (node.data.length !== dataStr.length) { if (node.data.length !== data_str.length) {
return node.splitText(dataStr.length); return node.splitText(data_str.length);
} }
} else { } else {
node.data = dataStr; node.data = data_str;
} }
}, },
() => text(data), () => text(data),

@ -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 = '4.2.0'; export const VERSION = '4.2.1';
export const PUBLIC_VERSION = '4'; export const PUBLIC_VERSION = '4';

@ -1,6 +1,8 @@
{ {
"plugins": ["lube"],
"rules": { "rules": {
"no-console": "off", "no-console": "off",
"@typescript-eslint/no-var-requires": "off" "@typescript-eslint/no-var-requires": "off",
"lube/svelte-naming-convention": ["error", { "fixSameNames": true }]
} }
} }

@ -20,14 +20,14 @@ describe('compiler-errors', () => {
it_fn(dir, () => { it_fn(dir, () => {
const cwd = path.resolve(`${__dirname}/samples/${dir}`); const cwd = path.resolve(`${__dirname}/samples/${dir}`);
const compileOptions = Object.assign({}, config.compileOptions || {}, { const compile_options = Object.assign({}, config.compileOptions || {}, {
immutable: config.immutable, immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true, accessors: 'accessors' in config ? config.accessors : true,
generate: 'dom' generate: 'dom'
}); });
try { try {
compile(fs.readFileSync(`${cwd}/main.svelte`, 'utf-8'), compileOptions); compile(fs.readFileSync(`${cwd}/main.svelte`, 'utf-8'), compile_options);
} catch (error) { } catch (error) {
if (typeof config.error === 'function') { if (typeof config.error === 'function') {
config.error(assert, error); config.error(assert, error);

@ -2,11 +2,11 @@ export default {
compileOptions: { compileOptions: {
filename: 'src/components/FooSwitcher.svelte', filename: 'src/components/FooSwitcher.svelte',
cssHash({ hash, css, name, filename }) { cssHash({ hash, css, name, filename }) {
const minFilename = filename const min_filename = filename
.split('/') .split('/')
.map((i) => i.charAt(0).toLowerCase()) .map((i) => i.charAt(0).toLowerCase())
.join(''); .join('');
return `sv-${name}-${minFilename}-${hash(css)}`; return `sv-${name}-${min_filename}-${hash(css)}`;
} }
} }
}; };

@ -1,10 +1,11 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as path from 'node:path'; import * as path from 'node:path';
import glob from 'tiny-glob/sync';
import colors from 'kleur';
import { assert } from 'vitest'; import { assert } from 'vitest';
import colors from 'kleur';
import { compile } from 'svelte/compiler'; import { compile } from 'svelte/compiler';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import glob from 'tiny-glob/sync';
export function try_load_json(file) { export function try_load_json(file) {
try { try {
@ -158,8 +159,8 @@ export function create_loader(compileOptions, cwd) {
) )
.replace( .replace(
/^import (\w+, )?{([^}]+)} from ['"](.+)['"];?/gm, /^import (\w+, )?{([^}]+)} from ['"](.+)['"];?/gm,
(_, default_, names, source) => { (_, _default, names, source) => {
const d = default_ ? `default: ${default_}` : ''; const d = _default ? `default: ${_default}` : '';
return `const { ${d} ${names.replaceAll( return `const { ${d} ${names.replaceAll(
' as ', ' as ',
': ' ': '

@ -18,12 +18,12 @@ describe('hydration', async () => {
it_fn(dir, async () => { it_fn(dir, async () => {
const cwd = path.resolve(`${__dirname}/samples/${dir}`); const cwd = path.resolve(`${__dirname}/samples/${dir}`);
const compileOptions = Object.assign({}, config.compileOptions, { const compile_options = Object.assign({}, config.compileOptions, {
accessors: 'accessors' in config ? config.accessors : true, accessors: 'accessors' in config ? config.accessors : true,
hydratable: true hydratable: true
}); });
const { default: SvelteComponent } = await create_loader(compileOptions, cwd)('main.svelte'); const { default: SvelteComponent } = await create_loader(compile_options, cwd)('main.svelte');
const target = window.document.body; const target = window.document.body;
const head = window.document.head; const head = window.document.head;

@ -2,12 +2,12 @@ export default {
props: {}, props: {},
snapshot(target) { snapshot(target) {
const nullText = target.querySelectorAll('p')[0].textContent; const null_text = target.querySelectorAll('p')[0].textContent;
const undefinedText = target.querySelectorAll('p')[1].textContent; const undefined_text = target.querySelectorAll('p')[1].textContent;
return { return {
nullText, nullText: null_text,
undefinedText undefinedText: undefined_text
}; };
} }
}; };

@ -53,11 +53,11 @@ function create_fragment(ctx) {
div7 = element("div"); div7 = element("div");
t7 = space(); t7 = space();
div8 = element("div"); div8 = element("div");
toggle_class(div0, "update1", reactiveModuleVar); toggle_class(div0, "update2", /*reactiveConst*/ ctx[0].x);
toggle_class(div1, "update2", /*reactiveConst*/ ctx[0].x); toggle_class(div1, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x);
toggle_class(div2, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x); toggle_class(div2, "update4", /*$reactiveStoreVal*/ ctx[2]);
toggle_class(div3, "update4", /*$reactiveStoreVal*/ ctx[2]); toggle_class(div3, "update5", /*$reactiveDeclaration*/ ctx[3]);
toggle_class(div4, "update5", /*$reactiveDeclaration*/ ctx[3]); toggle_class(div4, "update1", reassignedModuleVar);
toggle_class(div5, "static1", nonReactiveModuleVar); toggle_class(div5, "static1", nonReactiveModuleVar);
toggle_class(div6, "static2", nonReactiveGlobal); toggle_class(div6, "static2", nonReactiveGlobal);
toggle_class(div7, "static3", nonReactiveModuleVar && nonReactiveGlobal); toggle_class(div7, "static3", nonReactiveModuleVar && nonReactiveGlobal);
@ -83,24 +83,20 @@ function create_fragment(ctx) {
insert(target, div8, anchor); insert(target, div8, anchor);
}, },
p(ctx, [dirty]) { p(ctx, [dirty]) {
if (dirty & /*reactiveModuleVar*/ 0) {
toggle_class(div0, "update1", reactiveModuleVar);
}
if (dirty & /*reactiveConst*/ 1) { if (dirty & /*reactiveConst*/ 1) {
toggle_class(div1, "update2", /*reactiveConst*/ ctx[0].x); toggle_class(div0, "update2", /*reactiveConst*/ ctx[0].x);
} }
if (dirty & /*nonReactiveGlobal, reactiveConst*/ 1) { if (dirty & /*nonReactiveGlobal, reactiveConst*/ 1) {
toggle_class(div2, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x); toggle_class(div1, "update3", nonReactiveGlobal && /*reactiveConst*/ ctx[0].x);
} }
if (dirty & /*$reactiveStoreVal*/ 4) { if (dirty & /*$reactiveStoreVal*/ 4) {
toggle_class(div3, "update4", /*$reactiveStoreVal*/ ctx[2]); toggle_class(div2, "update4", /*$reactiveStoreVal*/ ctx[2]);
} }
if (dirty & /*$reactiveDeclaration*/ 8) { if (dirty & /*$reactiveDeclaration*/ 8) {
toggle_class(div4, "update5", /*$reactiveDeclaration*/ ctx[3]); toggle_class(div3, "update5", /*$reactiveDeclaration*/ ctx[3]);
} }
}, },
i: noop, i: noop,
@ -130,7 +126,7 @@ function create_fragment(ctx) {
} }
let nonReactiveModuleVar = Math.random(); let nonReactiveModuleVar = Math.random();
let reactiveModuleVar = Math.random(); let reassignedModuleVar = Math.random();
function instance($$self, $$props, $$invalidate) { function instance($$self, $$props, $$invalidate) {
let reactiveDeclaration; let reactiveDeclaration;
@ -144,13 +140,13 @@ function instance($$self, $$props, $$invalidate) {
$$self.$$.on_destroy.push(() => $$unsubscribe_reactiveDeclaration()); $$self.$$.on_destroy.push(() => $$unsubscribe_reactiveDeclaration());
nonReactiveGlobal = Math.random(); nonReactiveGlobal = Math.random();
const reactiveConst = { x: Math.random() }; const reactiveConst = { x: Math.random() };
reactiveModuleVar += 1; reassignedModuleVar += 1;
if (Math.random()) { if (Math.random()) {
reactiveConst.x += 1; reactiveConst.x += 1;
} }
$: $$subscribe_reactiveDeclaration($$invalidate(1, reactiveDeclaration = reactiveModuleVar * 2)); $: $$subscribe_reactiveDeclaration($$invalidate(1, reactiveDeclaration = reassignedModuleVar * 2));
return [reactiveConst, reactiveDeclaration, $reactiveStoreVal, $reactiveDeclaration]; return [reactiveConst, reactiveDeclaration, $reactiveStoreVal, $reactiveDeclaration];
} }

@ -1,6 +1,6 @@
<script context="module"> <script context="module">
let nonReactiveModuleVar = Math.random(); let nonReactiveModuleVar = Math.random();
let reactiveModuleVar = Math.random(); let reassignedModuleVar = Math.random();
</script> </script>
<script> <script>
@ -9,22 +9,22 @@
nonReactiveGlobal = Math.random(); nonReactiveGlobal = Math.random();
const reactiveConst = {x: Math.random()}; const reactiveConst = {x: Math.random()};
$: reactiveDeclaration = reactiveModuleVar * 2; $: reactiveDeclaration = reassignedModuleVar * 2;
reactiveModuleVar += 1; reassignedModuleVar += 1;
if (Math.random()) { if (Math.random()) {
reactiveConst.x += 1; reactiveConst.x += 1;
} }
</script> </script>
<!--These should all get updaters because they have at least one reactive dependency--> <!--These should all get updaters because they have at least one reactive dependency-->
<div class:update1={reactiveModuleVar}></div>
<div class:update2={reactiveConst.x}></div> <div class:update2={reactiveConst.x}></div>
<div class:update3={nonReactiveGlobal && reactiveConst.x}></div> <div class:update3={nonReactiveGlobal && reactiveConst.x}></div>
<div class:update4={$reactiveStoreVal}></div> <div class:update4={$reactiveStoreVal}></div>
<div class:update5={$reactiveDeclaration}></div> <div class:update5={$reactiveDeclaration}></div>
<!--These shouldn't get updates because they're purely non-reactive--> <!--These shouldn't get updates because they're purely non-reactive-->
<div class:update1={reassignedModuleVar}></div>
<div class:static1={nonReactiveModuleVar}></div> <div class:static1={nonReactiveModuleVar}></div>
<div class:static2={nonReactiveGlobal}></div> <div class:static2={nonReactiveGlobal}></div>
<div class:static3={nonReactiveModuleVar && nonReactiveGlobal}></div> <div class:static3={nonReactiveModuleVar && nonReactiveGlobal}></div>

@ -21,7 +21,7 @@ function create_dynamic_element_3(ctx) {
return { return {
c() { c() {
svelte_element = element(static_value); svelte_element = element(static_value);
set_dynamic_element_data(static_value)(svelte_element, { static_value, ...static_obj }); set_dynamic_element_data(static_value)(svelte_element, { static_value, .../*static_obj*/ ctx[2] });
toggle_class(svelte_element, "foo", static_value); toggle_class(svelte_element, "foo", static_value);
}, },
m(target, anchor) { m(target, anchor) {
@ -43,7 +43,7 @@ function create_dynamic_element_2(ctx) {
return { return {
c() { c() {
svelte_element = element(/*dynamic_value*/ ctx[0]); svelte_element = element(/*dynamic_value*/ ctx[0]);
set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, { static_value, ...static_obj }); set_dynamic_element_data(/*dynamic_value*/ ctx[0])(svelte_element, { static_value, .../*static_obj*/ ctx[2] });
toggle_class(svelte_element, "foo", static_value); toggle_class(svelte_element, "foo", static_value);
}, },
m(target, anchor) { m(target, anchor) {

@ -26,8 +26,8 @@ describe('parse', () => {
.trimEnd() .trimEnd()
.replace(/\r/g, ''); .replace(/\r/g, '');
const expectedOutput = try_load_json(`${__dirname}/samples/${dir}/output.json`); const expected_output = try_load_json(`${__dirname}/samples/${dir}/output.json`);
const expectedError = try_load_json(`${__dirname}/samples/${dir}/error.json`); const expected_error = try_load_json(`${__dirname}/samples/${dir}/error.json`);
try { try {
const { ast } = svelte.compile( const { ast } = svelte.compile(
@ -42,16 +42,16 @@ describe('parse', () => {
JSON.stringify(ast, null, '\t') JSON.stringify(ast, null, '\t')
); );
assert.deepEqual(ast.html, expectedOutput.html); assert.deepEqual(ast.html, expected_output.html);
assert.deepEqual(ast.css, expectedOutput.css); assert.deepEqual(ast.css, expected_output.css);
assert.deepEqual(ast.instance, expectedOutput.instance); assert.deepEqual(ast.instance, expected_output.instance);
assert.deepEqual(ast.module, expectedOutput.module); assert.deepEqual(ast.module, expected_output.module);
} catch (err) { } catch (err) {
if (err.name !== 'ParseError') throw err; if (err.name !== 'ParseError') throw err;
if (!expectedError) throw err; if (!expected_error) throw err;
const { code, message, pos, start } = err; const { code, message, pos, start } = err;
assert.deepEqual({ code, message, pos, start }, expectedError); assert.deepEqual({ code, message, pos, start }, expected_error);
} }
}); });
}); });

@ -33,10 +33,10 @@ export function ok(condition, message) {
} }
export function htmlEqual(actual, expected, message) { export function htmlEqual(actual, expected, message) {
return deepEqual(normalizeHtml(window, actual), normalizeHtml(window, expected), message); return deepEqual(normalize_html(window, actual), normalize_html(window, expected), message);
} }
function normalizeHtml(window, html) { function normalize_html(window, html) {
try { try {
const node = window.document.createElement('div'); const node = window.document.createElement('div');
node.innerHTML = html node.innerHTML = html
@ -44,7 +44,7 @@ function normalizeHtml(window, html) {
.replace(/>[\s\r\n]+</g, '><') .replace(/>[\s\r\n]+</g, '><')
.trim(); .trim();
normalizeStyles(node); normalize_styles(node);
return node.innerHTML.replace(/<\/?noscript\/?>/g, ''); return node.innerHTML.replace(/<\/?noscript\/?>/g, '');
} catch (err) { } catch (err) {
@ -52,14 +52,14 @@ function normalizeHtml(window, html) {
} }
} }
function normalizeStyles(node) { function normalize_styles(node) {
if (node.nodeType === 1) { if (node.nodeType === 1) {
if (node.hasAttribute('style')) { if (node.hasAttribute('style')) {
node.style = node.style.cssText; node.style = node.style.cssText;
} }
for (const child of node.childNodes) { for (const child of node.childNodes) {
normalizeStyles(child); normalize_styles(child);
} }
} }
} }

@ -93,7 +93,7 @@ async function run_browser_test(dir) {
globalName: 'test' globalName: 'test'
}); });
function assertWarnings() { function assert_warnings() {
if (config.warnings) { if (config.warnings) {
assert.deepStrictEqual( assert.deepStrictEqual(
warnings.map((w) => ({ warnings.map((w) => ({
@ -112,7 +112,7 @@ async function run_browser_test(dir) {
} }
} }
assertWarnings(); assert_warnings();
try { try {
const page = await browser.newPage(); const page = await browser.newPage();
@ -191,7 +191,7 @@ async function run_custom_elements_test(dir) {
globalName: 'test' globalName: 'test'
}); });
function assertWarnings() { function assert_warnings() {
if (expected_warnings) { if (expected_warnings) {
assert.deepStrictEqual( assert.deepStrictEqual(
warnings.map((w) => ({ warnings.map((w) => ({
@ -205,7 +205,7 @@ async function run_custom_elements_test(dir) {
); );
} }
} }
assertWarnings(); assert_warnings();
const page = await browser.newPage(); const page = await browser.newPage();
page.on('console', (type) => { page.on('console', (type) => {

@ -6,25 +6,25 @@ export default async function (target) {
target.innerHTML = '<custom-element red white></custom-element>'; target.innerHTML = '<custom-element red white></custom-element>';
await tick(); await tick();
await tick(); await tick();
const ceRoot = target.querySelector('custom-element').shadowRoot; const ce_root = target.querySelector('custom-element').shadowRoot;
const div = ceRoot.querySelector('div'); const div = ce_root.querySelector('div');
const p = ceRoot.querySelector('p'); const p = ce_root.querySelector('p');
const button = ceRoot.querySelector('button'); const button = ce_root.querySelector('button');
assert.equal(getComputedStyle(div).color, 'rgb(255, 0, 0)'); assert.equal(getComputedStyle(div).color, 'rgb(255, 0, 0)');
assert.equal(getComputedStyle(p).color, 'rgb(255, 255, 255)'); assert.equal(getComputedStyle(p).color, 'rgb(255, 255, 255)');
const innerRoot = ceRoot.querySelector('my-widget').shadowRoot; const inner_root = ce_root.querySelector('my-widget').shadowRoot;
const innerDiv = innerRoot.querySelector('div'); const inner_div = inner_root.querySelector('div');
const innerP = innerRoot.querySelector('p'); const inner_p = inner_root.querySelector('p');
assert.equal(getComputedStyle(innerDiv).color, 'rgb(255, 0, 0)'); assert.equal(getComputedStyle(inner_div).color, 'rgb(255, 0, 0)');
assert.equal(getComputedStyle(innerP).color, 'rgb(255, 255, 255)'); assert.equal(getComputedStyle(inner_p).color, 'rgb(255, 255, 255)');
button.click(); button.click();
await tick(); await tick();
await tick(); await tick();
assert.equal(getComputedStyle(div).color, 'rgb(0, 0, 0)'); assert.equal(getComputedStyle(div).color, 'rgb(0, 0, 0)');
assert.equal(getComputedStyle(innerDiv).color, 'rgb(0, 0, 0)'); assert.equal(getComputedStyle(inner_div).color, 'rgb(0, 0, 0)');
} }

@ -30,7 +30,7 @@ export default async function (target) {
const component = new SvelteComponent(options); const component = new SvelteComponent(options);
const waitUntil = async (fn, ms = 500) => { const wait_until = async (fn, ms = 500) => {
const start = new Date().getTime(); const start = new Date().getTime();
do { do {
if (fn()) return; if (fn()) return;
@ -48,7 +48,7 @@ export default async function (target) {
component, component,
target, target,
window, window,
waitUntil waitUntil: wait_until
}); });
component.$destroy(); component.$destroy();

@ -45,14 +45,14 @@ export default {
` `
); );
const circleColor1 = target.querySelector('#svg-1 circle'); const circle_color1 = target.querySelector('#svg-1 circle');
const rectColor1 = target.querySelector('#svg-1 rect'); const rect_color1 = target.querySelector('#svg-1 rect');
const circleColor2 = target.querySelector('#svg-2 circle'); const circle_color2 = target.querySelector('#svg-2 circle');
const rectColor2 = target.querySelector('#svg-2 rect'); const rect_color2 = target.querySelector('#svg-2 rect');
assert.htmlEqual(window.getComputedStyle(circleColor1).fill, 'rgb(255, 0, 0)'); assert.htmlEqual(window.getComputedStyle(circle_color1).fill, 'rgb(255, 0, 0)');
assert.htmlEqual(window.getComputedStyle(rectColor1).fill, 'rgb(255, 255, 0)'); assert.htmlEqual(window.getComputedStyle(rect_color1).fill, 'rgb(255, 255, 0)');
assert.htmlEqual(window.getComputedStyle(circleColor2).fill, 'rgb(0, 255, 255)'); assert.htmlEqual(window.getComputedStyle(circle_color2).fill, 'rgb(0, 255, 255)');
assert.htmlEqual(window.getComputedStyle(rectColor2).fill, 'rgb(0, 0, 0)'); assert.htmlEqual(window.getComputedStyle(rect_color2).fill, 'rgb(0, 0, 0)');
} }
}; };

@ -14,14 +14,14 @@ export default {
</div> </div>
`, `,
test({ target, window, assert }) { test({ target, window, assert }) {
const railColor1 = target.querySelector('#slider-1 p'); const rail_color1 = target.querySelector('#slider-1 p');
const trackColor1 = target.querySelector('#slider-1 span'); const track_color1 = target.querySelector('#slider-1 span');
const railColor2 = target.querySelector('#slider-2 p'); const rail_color2 = target.querySelector('#slider-2 p');
const trackColor2 = target.querySelector('#slider-2 span'); const track_color2 = target.querySelector('#slider-2 span');
assert.htmlEqual(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.htmlEqual(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.htmlEqual(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.htmlEqual(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.htmlEqual(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.htmlEqual(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.htmlEqual(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.htmlEqual(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
} }
}; };

@ -18,31 +18,31 @@ export default {
`, `,
test({ target, window, assert, component }) { test({ target, window, assert, component }) {
function assert_slider_1() { function assert_slider_1() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider1'); assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider1'); assert.equal(rail_color2.textContent, 'Slider1');
} }
function assert_slider_2() { function assert_slider_2() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider2'); assert.equal(rail_color1.textContent, 'Slider2');
assert.equal(railColor2.textContent, 'Slider2'); assert.equal(rail_color2.textContent, 'Slider2');
} }
assert_slider_1(); assert_slider_1();

@ -20,31 +20,31 @@ export default {
`, `,
test({ target, window, assert, component }) { test({ target, window, assert, component }) {
function assert_slider_1() { function assert_slider_1() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider1'); assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider1'); assert.equal(rail_color2.textContent, 'Slider1');
} }
function assert_slider_2() { function assert_slider_2() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(railColor1.textContent, 'Slider2'); assert.equal(rail_color1.textContent, 'Slider2');
assert.equal(railColor2.textContent, 'Slider2'); assert.equal(rail_color2.textContent, 'Slider2');
} }
assert_slider_1(); assert_slider_1();

@ -22,26 +22,26 @@ export default {
</div> </div>
`, `,
test({ target, window, assert }) { test({ target, window, assert }) {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
const nestRailColor1 = target.querySelector('#nest-component1 p'); const nest_rail_color1 = target.querySelector('#nest-component1 p');
const nestTrackColor1 = target.querySelector('#nest-component1 span'); const nest_track_color1 = target.querySelector('#nest-component1 span');
const nestRailColor2 = target.querySelector('#nest-component2 p'); const nest_rail_color2 = target.querySelector('#nest-component2 p');
const nestTrackColor2 = target.querySelector('#nest-component2 span'); const nest_track_color2 = target.querySelector('#nest-component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor1).color, 'rgb(255, 255, 0)'); assert.equal(window.getComputedStyle(nest_rail_color1).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nestTrackColor1).color, 'rgb(255, 0, 255)'); assert.equal(window.getComputedStyle(nest_track_color1).color, 'rgb(255, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor2).color, 'rgb(0, 255, 255)'); assert.equal(window.getComputedStyle(nest_rail_color2).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nestTrackColor2).color, 'rgb(255, 255, 255)'); assert.equal(window.getComputedStyle(nest_track_color2).color, 'rgb(255, 255, 255)');
assert.equal(railColor1.textContent, 'Slider1'); assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider2'); assert.equal(rail_color2.textContent, 'Slider2');
assert.equal(nestRailColor1.textContent, 'Slider1'); assert.equal(nest_rail_color1.textContent, 'Slider1');
assert.equal(nestRailColor2.textContent, 'Slider2'); assert.equal(nest_rail_color2.textContent, 'Slider2');
} }
}; };

@ -25,51 +25,51 @@ export default {
`, `,
test({ target, window, assert, component }) { test({ target, window, assert, component }) {
function assert_slider_1() { function assert_slider_1() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
const nestRailColor1 = target.querySelector('#nest-component1 p'); const nest_rail_color1 = target.querySelector('#nest-component1 p');
const nestTrackColor1 = target.querySelector('#nest-component1 span'); const nest_track_color1 = target.querySelector('#nest-component1 span');
const nestRailColor2 = target.querySelector('#nest-component2 p'); const nest_rail_color2 = target.querySelector('#nest-component2 p');
const nestTrackColor2 = target.querySelector('#nest-component2 span'); const nest_track_color2 = target.querySelector('#nest-component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor1).color, 'rgb(255, 255, 0)'); assert.equal(window.getComputedStyle(nest_rail_color1).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nestTrackColor1).color, 'rgb(255, 0, 255)'); assert.equal(window.getComputedStyle(nest_track_color1).color, 'rgb(255, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor2).color, 'rgb(255, 255, 0)'); assert.equal(window.getComputedStyle(nest_rail_color2).color, 'rgb(255, 255, 0)');
assert.equal(window.getComputedStyle(nestTrackColor2).color, 'rgb(255, 0, 255)'); assert.equal(window.getComputedStyle(nest_track_color2).color, 'rgb(255, 0, 255)');
assert.equal(railColor1.textContent, 'Slider1'); assert.equal(rail_color1.textContent, 'Slider1');
assert.equal(railColor2.textContent, 'Slider1'); assert.equal(rail_color2.textContent, 'Slider1');
assert.equal(nestRailColor1.textContent, 'Slider1'); assert.equal(nest_rail_color1.textContent, 'Slider1');
assert.equal(nestRailColor2.textContent, 'Slider1'); assert.equal(nest_rail_color2.textContent, 'Slider1');
} }
function assert_slider_2() { function assert_slider_2() {
const railColor1 = target.querySelector('#component1 p'); const rail_color1 = target.querySelector('#component1 p');
const trackColor1 = target.querySelector('#component1 span'); const track_color1 = target.querySelector('#component1 span');
const railColor2 = target.querySelector('#component2 p'); const rail_color2 = target.querySelector('#component2 p');
const trackColor2 = target.querySelector('#component2 span'); const track_color2 = target.querySelector('#component2 span');
const nestRailColor1 = target.querySelector('#nest-component1 p'); const nest_rail_color1 = target.querySelector('#nest-component1 p');
const nestTrackColor1 = target.querySelector('#nest-component1 span'); const nest_track_color1 = target.querySelector('#nest-component1 span');
const nestRailColor2 = target.querySelector('#nest-component2 p'); const nest_rail_color2 = target.querySelector('#nest-component2 p');
const nestTrackColor2 = target.querySelector('#nest-component2 span'); const nest_track_color2 = target.querySelector('#nest-component2 span');
assert.equal(window.getComputedStyle(railColor1).color, 'rgb(0, 0, 0)'); assert.equal(window.getComputedStyle(rail_color1).color, 'rgb(0, 0, 0)');
assert.equal(window.getComputedStyle(trackColor1).color, 'rgb(255, 0, 0)'); assert.equal(window.getComputedStyle(track_color1).color, 'rgb(255, 0, 0)');
assert.equal(window.getComputedStyle(railColor2).color, 'rgb(0, 255, 0)'); assert.equal(window.getComputedStyle(rail_color2).color, 'rgb(0, 255, 0)');
assert.equal(window.getComputedStyle(trackColor2).color, 'rgb(0, 0, 255)'); assert.equal(window.getComputedStyle(track_color2).color, 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(nestRailColor1).color, 'rgb(0, 255, 255)'); assert.equal(window.getComputedStyle(nest_rail_color1).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nestTrackColor1).color, 'rgb(255, 255, 255)'); assert.equal(window.getComputedStyle(nest_track_color1).color, 'rgb(255, 255, 255)');
assert.equal(window.getComputedStyle(nestRailColor2).color, 'rgb(0, 255, 255)'); assert.equal(window.getComputedStyle(nest_rail_color2).color, 'rgb(0, 255, 255)');
assert.equal(window.getComputedStyle(nestTrackColor2).color, 'rgb(255, 255, 255)'); assert.equal(window.getComputedStyle(nest_track_color2).color, 'rgb(255, 255, 255)');
assert.equal(railColor1.textContent, 'Slider2'); assert.equal(rail_color1.textContent, 'Slider2');
assert.equal(railColor2.textContent, 'Slider2'); assert.equal(rail_color2.textContent, 'Slider2');
assert.equal(nestRailColor1.textContent, 'Slider2'); assert.equal(nest_rail_color1.textContent, 'Slider2');
assert.equal(nestRailColor2.textContent, 'Slider2'); assert.equal(nest_rail_color2.textContent, 'Slider2');
} }
assert_slider_1(); assert_slider_1();

@ -55,18 +55,18 @@ async function run_test(dir) {
const cwd = path.resolve(`${__dirname}/samples/${dir}`); const cwd = path.resolve(`${__dirname}/samples/${dir}`);
const compileOptions = Object.assign({}, config.compileOptions || {}, { const compile_options = Object.assign({}, config.compileOptions || {}, {
hydratable: hydrate, hydratable: hydrate,
immutable: config.immutable, immutable: config.immutable,
accessors: 'accessors' in config ? config.accessors : true accessors: 'accessors' in config ? config.accessors : true
}); });
const load = create_loader(compileOptions, cwd); const load = create_loader(compile_options, cwd);
let mod; let mod;
let SvelteComponent; let SvelteComponent;
let unintendedError = null; let unintended_error = null;
if (config.expect_unhandled_rejections) { if (config.expect_unhandled_rejections) {
listeners.forEach((listener) => { listeners.forEach((listener) => {
@ -111,7 +111,7 @@ async function run_test(dir) {
let snapshot = undefined; let snapshot = undefined;
if (hydrate && from_ssr_html) { if (hydrate && from_ssr_html) {
const load_ssr = create_loader({ ...compileOptions, generate: 'ssr' }, cwd); const load_ssr = create_loader({ ...compile_options, generate: 'ssr' }, cwd);
// ssr into target // ssr into target
if (config.before_test) config.before_test(); if (config.before_test) config.before_test();
@ -152,14 +152,14 @@ async function run_test(dir) {
console.warn = warn; console.warn = warn;
if (config.error) { if (config.error) {
unintendedError = true; unintended_error = true;
assert.fail('Expected a runtime error'); assert.fail('Expected a runtime error');
} }
if (config.warnings) { if (config.warnings) {
assert.deepEqual(warnings, config.warnings); assert.deepEqual(warnings, config.warnings);
} else if (warnings.length) { } else if (warnings.length) {
unintendedError = true; unintended_error = true;
assert.fail('Received unexpected warnings'); assert.fail('Received unexpected warnings');
} }
@ -183,7 +183,7 @@ async function run_test(dir) {
snapshot, snapshot,
window, window,
raf, raf,
compileOptions, compileOptions: compile_options,
load load
}); });
} }
@ -201,7 +201,7 @@ async function run_test(dir) {
await test() await test()
.catch((err) => { .catch((err) => {
if (config.error && !unintendedError) { if (config.error && !unintended_error) {
if (typeof config.error === 'function') { if (typeof config.error === 'function') {
config.error(assert, err); config.error(assert, err);
} else { } else {
@ -217,7 +217,7 @@ async function run_test(dir) {
mkdirp(path.dirname(out)); // file could be in subdirectory, therefore don't use dir mkdirp(path.dirname(out)); // file could be in subdirectory, therefore don't use dir
const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, ''), { const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, ''), {
...compileOptions, ...compile_options,
filename: file filename: file
}); });
fs.writeFileSync(out, js.code); fs.writeFileSync(out, js.code);

@ -10,9 +10,9 @@ export default {
`, `,
async test({ assert, target, window }) { async test({ assert, target, window }) {
const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button'); const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click'); const click_event = new window.MouseEvent('click');
await btn1.dispatchEvent(clickEvent); await btn1.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -24,7 +24,7 @@ export default {
` `
); );
await btn2.dispatchEvent(clickEvent); await btn2.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -36,7 +36,7 @@ export default {
` `
); );
await btn3.dispatchEvent(clickEvent); await btn3.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -48,7 +48,7 @@ export default {
` `
); );
await btn4.dispatchEvent(clickEvent); await btn4.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -12,9 +12,9 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button'); const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click'); const click_event = new window.MouseEvent('click');
await btn1.dispatchEvent(clickEvent); await btn1.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -27,7 +27,7 @@ export default {
` `
); );
await btn2.dispatchEvent(clickEvent); await btn2.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -40,7 +40,7 @@ export default {
` `
); );
await btn3.dispatchEvent(clickEvent); await btn3.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -53,7 +53,7 @@ export default {
` `
); );
await btn4.dispatchEvent(clickEvent); await btn4.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -5,10 +5,10 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const button = target.querySelector('button'); const button = target.querySelector('button');
const eventEnter = new window.MouseEvent('mouseenter'); const event_enter = new window.MouseEvent('mouseenter');
const eventLeave = new window.MouseEvent('mouseleave'); const event_leave = new window.MouseEvent('mouseleave');
await button.dispatchEvent(eventEnter); await button.dispatchEvent(event_enter);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -17,7 +17,7 @@ export default {
` `
); );
await button.dispatchEvent(eventLeave); await button.dispatchEvent(event_leave);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -7,7 +7,7 @@ export default {
const button = target.querySelector('button'); const button = target.querySelector('button');
const enter = new window.MouseEvent('mouseenter'); const enter = new window.MouseEvent('mouseenter');
const leave = new window.MouseEvent('mouseleave'); const leave = new window.MouseEvent('mouseleave');
const ctrlPress = new window.KeyboardEvent('keydown', { ctrlKey: true }); const ctrl_press = new window.KeyboardEvent('keydown', { ctrlKey: true });
await button.dispatchEvent(enter); await button.dispatchEvent(enter);
assert.htmlEqual( assert.htmlEqual(
@ -18,7 +18,7 @@ export default {
` `
); );
await window.dispatchEvent(ctrlPress); await window.dispatchEvent(ctrl_press);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -5,10 +5,10 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const button = target.querySelector('button'); const button = target.querySelector('button');
const eventEnter = new window.MouseEvent('mouseenter'); const event_enter = new window.MouseEvent('mouseenter');
const eventLeave = new window.MouseEvent('mouseleave'); const event_leave = new window.MouseEvent('mouseleave');
await button.dispatchEvent(eventEnter); await button.dispatchEvent(event_enter);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -17,7 +17,7 @@ export default {
` `
); );
await button.dispatchEvent(eventLeave); await button.dispatchEvent(event_leave);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -1,8 +1,8 @@
const realPromise = Promise.resolve(42); const real_promise = Promise.resolve(42);
const promise = () => {}; const promise = () => {};
promise.then = realPromise.then.bind(realPromise); promise.then = real_promise.then.bind(real_promise);
promise.catch = realPromise.catch.bind(realPromise); promise.catch = real_promise.catch.bind(real_promise);
export default { export default {
get props() { get props() {

@ -1,13 +1,13 @@
let fulfil; let fulfil;
const thePromise = new Promise((f) => { const the_promise = new Promise((f) => {
fulfil = f; fulfil = f;
}); });
const items = [ const items = [
{ {
title: 'a title', title: 'a title',
data: thePromise data: the_promise
} }
]; ];
@ -23,7 +23,7 @@ export default {
test({ assert, target }) { test({ assert, target }) {
fulfil(42); fulfil(42);
return thePromise.then(() => { return the_promise.then(() => {
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -28,10 +28,10 @@ export default {
assert.equal(component.clicked, 42); assert.equal(component.clicked, 42);
const thePromise = Promise.resolve(43); const the_promise = Promise.resolve(43);
component.thePromise = thePromise; component.thePromise = the_promise;
return thePromise; return the_promise;
}) })
.then(() => { .then(() => {
const { button } = component; const { button } = component;

@ -1,12 +1,12 @@
let fulfil; let fulfil;
const thePromise = new Promise((f) => { const the_promise = new Promise((f) => {
fulfil = f; fulfil = f;
}); });
export default { export default {
get props() { get props() {
return { show: true, thePromise }; return { show: true, thePromise: the_promise };
}, },
html: ` html: `
@ -16,7 +16,7 @@ export default {
test({ assert, component, target }) { test({ assert, component, target }) {
fulfil(42); fulfil(42);
return thePromise.then(() => { return the_promise.then(() => {
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -35,7 +35,7 @@ export default {
component.show = true; component.show = true;
return thePromise.then(() => { return the_promise.then(() => {
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -1,12 +1,12 @@
let fulfil; let fulfil;
const thePromise = new Promise((f) => { const the_promise = new Promise((f) => {
fulfil = f; fulfil = f;
}); });
export default { export default {
get props() { get props() {
return { thePromise }; return { thePromise: the_promise };
}, },
html: ` html: `
@ -16,7 +16,7 @@ export default {
test({ assert, target }) { test({ assert, target }) {
fulfil(42); fulfil(42);
return thePromise.then(() => { return the_promise.then(() => {
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -1,12 +1,12 @@
let fulfil; let fulfil;
const thePromise = new Promise((f) => { const the_promise = new Promise((f) => {
fulfil = f; fulfil = f;
}); });
export default { export default {
get props() { get props() {
return { thePromise }; return { thePromise: the_promise };
}, },
html: ` html: `
@ -16,7 +16,7 @@ export default {
async test({ assert, target }) { async test({ assert, target }) {
fulfil([]); fulfil([]);
await thePromise; await the_promise;
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -15,12 +15,12 @@ export default {
test({ assert, component, target, window }) { test({ assert, component, target, window }) {
const { cats } = component; const { cats } = component;
const newCats = cats.slice(); const new_cats = cats.slice();
newCats.push({ new_cats.push({
name: 'cat ' + cats.length, name: 'cat ' + cats.length,
checked: false checked: false
}); });
component.cats = newCats; component.cats = new_cats;
let inputs = target.querySelectorAll('input'); let inputs = target.querySelectorAll('input');
assert.equal(inputs.length, 3); assert.equal(inputs.length, 3);

@ -23,7 +23,7 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const inputs = target.querySelectorAll('input'); const inputs = target.querySelectorAll('input');
const checked = new Set(); const checked = new Set();
const checkInbox = async (i) => { const check_inbox = async (i) => {
checked.add(i); checked.add(i);
inputs[i].checked = true; inputs[i].checked = true;
await inputs[i].dispatchEvent(event); await inputs[i].dispatchEvent(event);
@ -35,17 +35,17 @@ export default {
const event = new window.Event('change'); const event = new window.Event('change');
await checkInbox(2); await check_inbox(2);
for (let i = 0; i < 18; i++) { for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i)); assert.equal(inputs[i].checked, checked.has(i));
} }
await checkInbox(12); await check_inbox(12);
for (let i = 0; i < 18; i++) { for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i)); assert.equal(inputs[i].checked, checked.has(i));
} }
await checkInbox(8); await check_inbox(8);
for (let i = 0; i < 18; i++) { for (let i = 0; i < 18; i++) {
assert.equal(inputs[i].checked, checked.has(i)); assert.equal(inputs[i].checked, checked.has(i));
} }

@ -1,8 +1,8 @@
export default { export default {
async test({ assert, target, component, window }) { async test({ assert, target, component, window }) {
const button = target.querySelector('button'); const button = target.querySelector('button');
const clickEvent = new window.Event('click'); const click_event = new window.Event('click');
const changeEvent = new window.Event('change'); const change_event = new window.Event('change');
const [input1, input2] = target.querySelectorAll('input[type="checkbox"]'); const [input1, input2] = target.querySelectorAll('input[type="checkbox"]');
function validate_inputs(v1, v2) { function validate_inputs(v1, v2) {
@ -17,24 +17,24 @@ export default {
validate_inputs(true, true); validate_inputs(true, true);
input1.checked = false; input1.checked = false;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, ['b']); assert.deepEqual(component.test, ['b']);
input2.checked = false; input2.checked = false;
await input2.dispatchEvent(changeEvent); await input2.dispatchEvent(change_event);
assert.deepEqual(component.test, []); assert.deepEqual(component.test, []);
input1.checked = true; input1.checked = true;
input2.checked = true; input2.checked = true;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
await input2.dispatchEvent(changeEvent); await input2.dispatchEvent(change_event);
assert.deepEqual(component.test, ['b', 'a']); assert.deepEqual(component.test, ['b', 'a']);
await button.dispatchEvent(clickEvent); await button.dispatchEvent(click_event);
assert.deepEqual(component.test, ['b', 'a']); // should it be ['a'] only? valid arguments for both outcomes assert.deepEqual(component.test, ['b', 'a']); // should it be ['a'] only? valid arguments for both outcomes
input1.checked = false; input1.checked = false;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, []); assert.deepEqual(component.test, []);
} }
}; };

@ -1,8 +1,8 @@
export default { export default {
async test({ assert, target, component, window }) { async test({ assert, target, component, window }) {
const button = target.querySelector('button'); const button = target.querySelector('button');
const clickEvent = new window.Event('click'); const click_event = new window.Event('click');
const changeEvent = new window.Event('change'); const change_event = new window.Event('change');
const [input1, input2] = target.querySelectorAll('input[type="radio"]'); const [input1, input2] = target.querySelectorAll('input[type="radio"]');
function validate_inputs(v1, v2) { function validate_inputs(v1, v2) {
@ -17,18 +17,18 @@ export default {
validate_inputs(false, true); validate_inputs(false, true);
input1.checked = true; input1.checked = true;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, 'a'); assert.deepEqual(component.test, 'a');
input2.checked = true; input2.checked = true;
await input2.dispatchEvent(changeEvent); await input2.dispatchEvent(change_event);
assert.deepEqual(component.test, 'b'); assert.deepEqual(component.test, 'b');
await button.dispatchEvent(clickEvent); await button.dispatchEvent(click_event);
assert.deepEqual(component.test, 'b'); // should it be undefined? valid arguments for both outcomes assert.deepEqual(component.test, 'b'); // should it be undefined? valid arguments for both outcomes
input1.checked = true; input1.checked = true;
await input1.dispatchEvent(changeEvent); await input1.dispatchEvent(change_event);
assert.deepEqual(component.test, 'a'); assert.deepEqual(component.test, 'a');
} }
}; };

@ -1,25 +1,25 @@
export default { export default {
test({ assert, target, window, component }) { test({ assert, target, window, component }) {
const input = target.querySelector('input'); const input = target.querySelector('input');
const inputEvent = new window.InputEvent('input'); const input_event = new window.InputEvent('input');
assert.equal(component.value, 5); assert.equal(component.value, 5);
assert.equal(input.value, '5'); assert.equal(input.value, '5');
input.value = '5.'; input.value = '5.';
input.dispatchEvent(inputEvent); input.dispatchEvent(input_event);
// input type number has value === "" if ends with dot/comma // input type number has value === "" if ends with dot/comma
assert.equal(component.value, undefined); assert.equal(component.value, undefined);
assert.equal(input.value, ''); assert.equal(input.value, '');
input.value = '5.5'; input.value = '5.5';
input.dispatchEvent(inputEvent); input.dispatchEvent(input_event);
assert.equal(component.value, 5.5); assert.equal(component.value, 5.5);
assert.equal(input.value, '5.5'); assert.equal(input.value, '5.5');
input.value = '5.50'; input.value = '5.50';
input.dispatchEvent(inputEvent); input.dispatchEvent(input_event);
assert.equal(component.value, 5.5); assert.equal(component.value, 5.5);
assert.equal(input.value, '5.50'); assert.equal(input.value, '5.50');

@ -9,13 +9,13 @@ const components = [
} }
]; ];
const selectedComponent = components[0]; const selected_component = components[0];
export default { export default {
skip: true, // doesn't reflect real-world bug, maybe a JSDOM quirk skip: true, // doesn't reflect real-world bug, maybe a JSDOM quirk
get props() { get props() {
return { components, selectedComponent }; return { components, selectedComponent: selected_component };
}, },
html: ` html: `

@ -5,13 +5,13 @@ export default {
`, `,
async test({ assert, component, target, window }) { async test({ assert, component, target, window }) {
const [updateButton, button] = target.querySelectorAll('button'); const [update_button, button] = target.querySelectorAll('button');
const event = new window.MouseEvent('click'); const event = new window.MouseEvent('click');
await button.dispatchEvent(event); await button.dispatchEvent(event);
assert.equal(component.count, 1); assert.equal(component.count, 1);
await updateButton.dispatchEvent(event); await update_button.dispatchEvent(event);
await button.dispatchEvent(event); await button.dispatchEvent(event);
assert.equal(component.count, 11); assert.equal(component.count, 11);
} }

@ -5,10 +5,10 @@ export default {
`, `,
async test({ assert, component, target, window }) { async test({ assert, component, target, window }) {
const [updateButton, button] = target.querySelectorAll('button'); const [update_button, button] = target.querySelectorAll('button');
const event = new window.MouseEvent('click'); const event = new window.MouseEvent('click');
await updateButton.dispatchEvent(event); await update_button.dispatchEvent(event);
await button.dispatchEvent(event); await button.dispatchEvent(event);
assert.equal(component.count, 10); assert.equal(component.count, 10);

@ -3,7 +3,7 @@ export default {
ssrHtml: '<input value="Blub"> <input value="Blub"> <input value="Blub">', ssrHtml: '<input value="Blub"> <input value="Blub"> <input value="Blub">',
async test({ assert, target, component, window }) { async test({ assert, target, component, window }) {
const [input1, input2, inputFallback] = target.querySelectorAll('input'); const [input1, input2, input_fallback] = target.querySelectorAll('input');
assert.equal(component.getSubscriberCount(), 3); assert.equal(component.getSubscriberCount(), 3);
@ -13,7 +13,7 @@ export default {
await input1.dispatchEvent(new window.Event('input')); await input1.dispatchEvent(new window.Event('input'));
assert.equal(input1.value, 'ab'); assert.equal(input1.value, 'ab');
assert.equal(input2.value, 'ab'); assert.equal(input2.value, 'ab');
assert.equal(inputFallback.value, 'ab'); assert.equal(input_fallback.value, 'ab');
component.props = 'hello'; component.props = 'hello';

@ -6,9 +6,9 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const btn = target.querySelector('button'); const btn = target.querySelector('button');
const clickEvent = new window.MouseEvent('click'); const click_event = new window.MouseEvent('click');
await btn.dispatchEvent(clickEvent); await btn.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -18,7 +18,7 @@ export default {
` `
); );
await btn.dispatchEvent(clickEvent); await btn.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -1,6 +1,6 @@
let originalDivGetBoundingClientRect; let original_div_get_bounding_client_rect;
let originalSpanGetBoundingClientRect; let original_span_get_bounding_client_rect;
let originalParagraphGetBoundingClientRect; let original_paragraph_get_bounding_client_rect;
export default { export default {
skip_if_ssr: true, skip_if_ssr: true,
@ -26,16 +26,16 @@ export default {
`, `,
before_test() { before_test() {
originalDivGetBoundingClientRect = window.HTMLDivElement.prototype.getBoundingClientRect; original_div_get_bounding_client_rect = window.HTMLDivElement.prototype.getBoundingClientRect;
originalSpanGetBoundingClientRect = window.HTMLSpanElement.prototype.getBoundingClientRect; original_span_get_bounding_client_rect = window.HTMLSpanElement.prototype.getBoundingClientRect;
originalParagraphGetBoundingClientRect = original_paragraph_get_bounding_client_rect =
window.HTMLParagraphElement.prototype.getBoundingClientRect; window.HTMLParagraphElement.prototype.getBoundingClientRect;
window.HTMLDivElement.prototype.getBoundingClientRect = fakeGetBoundingClientRect; window.HTMLDivElement.prototype.getBoundingClientRect = fake_get_bounding_client_rect;
window.HTMLSpanElement.prototype.getBoundingClientRect = fakeGetBoundingClientRect; window.HTMLSpanElement.prototype.getBoundingClientRect = fake_get_bounding_client_rect;
window.HTMLParagraphElement.prototype.getBoundingClientRect = fakeGetBoundingClientRect; window.HTMLParagraphElement.prototype.getBoundingClientRect = fake_get_bounding_client_rect;
function fakeGetBoundingClientRect() { function fake_get_bounding_client_rect() {
const index = [...this.parentNode.children].indexOf(this); const index = [...this.parentNode.children].indexOf(this);
const top = index * 30; const top = index * 30;
@ -48,10 +48,10 @@ export default {
} }
}, },
after_test() { after_test() {
window.HTMLDivElement.prototype.getBoundingClientRect = originalDivGetBoundingClientRect; window.HTMLDivElement.prototype.getBoundingClientRect = original_div_get_bounding_client_rect;
window.HTMLSpanElement.prototype.getBoundingClientRect = originalSpanGetBoundingClientRect; window.HTMLSpanElement.prototype.getBoundingClientRect = original_span_get_bounding_client_rect;
window.HTMLParagraphElement.prototype.getBoundingClientRect = window.HTMLParagraphElement.prototype.getBoundingClientRect =
originalParagraphGetBoundingClientRect; original_paragraph_get_bounding_client_rect;
}, },
async test({ assert, component, raf }) { async test({ assert, component, raf }) {

@ -0,0 +1,3 @@
export default {
html: '<div id="element" class="element-handler">this is div</div>'
};

@ -0,0 +1,7 @@
<script>
let props = {
id: "element",
class: "element-handler"
}
</script>
<svelte:element this={"div"} {...props}>this is div</svelte:element>

@ -13,10 +13,10 @@ export default {
assert.equal(input1.value, ''); assert.equal(input1.value, '');
assert.equal(input2.value, 'hello'); assert.equal(input2.value, 'hello');
const inputEvent = new window.InputEvent('input'); const input_event = new window.InputEvent('input');
input2.value = 'world'; input2.value = 'world';
input2.dispatchEvent(inputEvent); input2.dispatchEvent(input_event);
assert.equal(input2.value, 'world'); assert.equal(input2.value, 'world');
assert.equal(component.array[1].value, 'world'); assert.equal(component.array[1].value, 'world');
} }

@ -1,6 +1,6 @@
const VALUES = Array.from('abcdefghijklmnopqrstuvwxyz'); const VALUES = Array.from('abcdefghijklmnopqrstuvwxyz');
function toObjects(array) { function to_objects(array) {
return array.split('').map((x) => ({ id: x })); return array.split('').map((x) => ({ id: x }));
} }
@ -17,7 +17,7 @@ function permute() {
export default { export default {
get props() { get props() {
return { values: toObjects('abc') }; return { values: to_objects('abc') };
}, },
html: '(a)(b)(c)', html: '(a)(b)(c)',
@ -29,7 +29,7 @@ export default {
.split('') .split('')
.map((x) => `(${x})`) .map((x) => `(${x})`)
.join(''); .join('');
component.values = toObjects(sequence); component.values = to_objects(sequence);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
expected, expected,

@ -6,8 +6,8 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const button = target.querySelector('button'); const button = target.querySelector('button');
const clickEvent = new window.MouseEvent('click'); const click_event = new window.MouseEvent('click');
await button.dispatchEvent(clickEvent); await button.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -9,10 +9,10 @@ export default {
<button>Test</button> <button>Test</button>
`, `,
async test({ assert, target, window }) { async test({ assert, target, window }) {
let [incrementBtn, ...buttons] = target.querySelectorAll('button'); let [increment_btn, ...buttons] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click'); const click_event = new window.MouseEvent('click');
await buttons[0].dispatchEvent(clickEvent); await buttons[0].dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -27,7 +27,7 @@ export default {
` `
); );
await buttons[0].dispatchEvent(clickEvent); await buttons[0].dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -42,8 +42,8 @@ export default {
` `
); );
await buttons[2].dispatchEvent(clickEvent); await buttons[2].dispatchEvent(click_event);
await buttons[2].dispatchEvent(clickEvent); await buttons[2].dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -58,7 +58,7 @@ export default {
` `
); );
await incrementBtn.dispatchEvent(clickEvent); await increment_btn.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -75,9 +75,9 @@ export default {
` `
); );
[incrementBtn, ...buttons] = target.querySelectorAll('button'); [increment_btn, ...buttons] = target.querySelectorAll('button');
await buttons[3].dispatchEvent(clickEvent); await buttons[3].dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -9,7 +9,7 @@ export default {
`, `,
async test({ assert, target, window }) { async test({ assert, target, window }) {
const [updateButton1, updateButton2, button] = target.querySelectorAll('button'); const [update_button1, update_button2, button] = target.querySelectorAll('button');
const event = new window.MouseEvent('click'); const event = new window.MouseEvent('click');
let err = ''; let err = '';
@ -32,7 +32,7 @@ export default {
` `
); );
await updateButton1.dispatchEvent(event); await update_button1.dispatchEvent(event);
await button.dispatchEvent(event); await button.dispatchEvent(event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -46,7 +46,7 @@ export default {
` `
); );
await updateButton2.dispatchEvent(event); await update_button2.dispatchEvent(event);
await button.dispatchEvent(event); await button.dispatchEvent(event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -4,7 +4,7 @@ export default {
<button>invalid</button>`, <button>invalid</button>`,
async test({ assert, target, window }) { async test({ assert, target, window }) {
const [buttonUndef, buttonNull, buttonInvalid] = target.querySelectorAll('button'); const [button_undef, button_null, button_invalid] = target.querySelectorAll('button');
const event = new window.MouseEvent('click'); const event = new window.MouseEvent('click');
let err = ''; let err = '';
@ -14,13 +14,13 @@ export default {
}); });
// All three should not throw if proper checking is done in runtime code // All three should not throw if proper checking is done in runtime code
await buttonUndef.dispatchEvent(event); await button_undef.dispatchEvent(event);
assert.equal(err, '', err); assert.equal(err, '', err);
await buttonNull.dispatchEvent(event); await button_null.dispatchEvent(event);
assert.equal(err, '', err); assert.equal(err, '', err);
await buttonInvalid.dispatchEvent(event); await button_invalid.dispatchEvent(event);
assert.equal(err, '', err); assert.equal(err, '', err);
} }
}; };

@ -9,7 +9,7 @@ export default {
`, `,
async test({ assert, target, window }) { async test({ assert, target, window }) {
const [updateButton1, updateButton2, button] = target.querySelectorAll('button'); const [update_button1, update_button2, button] = target.querySelectorAll('button');
const event = new window.MouseEvent('click'); const event = new window.MouseEvent('click');
let err = ''; let err = '';
@ -32,7 +32,7 @@ export default {
` `
); );
await updateButton1.dispatchEvent(event); await update_button1.dispatchEvent(event);
await button.dispatchEvent(event); await button.dispatchEvent(event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -46,7 +46,7 @@ export default {
` `
); );
await updateButton2.dispatchEvent(event); await update_button2.dispatchEvent(event);
await button.dispatchEvent(event); await button.dispatchEvent(event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,

@ -10,21 +10,21 @@ export default {
assert.equal(component.updated, 4); assert.equal(component.updated, 4);
const [item1, item2] = target.childNodes; const [item1, item2] = target.childNodes;
const [item1Btn1, item1Btn2] = item1.querySelectorAll('button'); const [item1_btn1, item1_btn2] = item1.querySelectorAll('button');
const [item2Btn1, item2Btn2] = item2.querySelectorAll('button'); const [item2_btn1, item2_btn2] = item2.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click'); const click_event = new window.MouseEvent('click');
await item1Btn1.dispatchEvent(clickEvent); await item1_btn1.dispatchEvent(click_event);
assert.equal(component.getNormalCount(), 1); assert.equal(component.getNormalCount(), 1);
await item1Btn2.dispatchEvent(clickEvent); await item1_btn2.dispatchEvent(click_event);
assert.equal(component.getModifierCount(), 1); assert.equal(component.getModifierCount(), 1);
await item2Btn1.dispatchEvent(clickEvent); await item2_btn1.dispatchEvent(click_event);
assert.equal(component.getNormalCount(), 2); assert.equal(component.getNormalCount(), 2);
await item2Btn2.dispatchEvent(clickEvent); await item2_btn2.dispatchEvent(click_event);
assert.equal(component.getModifierCount(), 2); assert.equal(component.getModifierCount(), 2);
} }
}; };

@ -11,11 +11,11 @@ export default {
`, `,
test({ assert, component }) { test({ assert, component }) {
const visibleThings = component.visibleThings; const visible_things = component.visibleThings;
assert.deepEqual(visibleThings, ['first thing', 'second thing']); assert.deepEqual(visible_things, ['first thing', 'second thing']);
const snapshots = component.snapshots; const snapshots = component.snapshots;
assert.deepEqual(snapshots, [visibleThings]); assert.deepEqual(snapshots, [visible_things]);
// TODO minimise the number of recomputations during oncreate // TODO minimise the number of recomputations during oncreate
// assert.equal(counter.count, 1); // assert.equal(counter.count, 1);

@ -7,10 +7,14 @@ export default {
}, },
async test({ assert, target }) { async test({ assert, target }) {
const firstSpanList = target.children[0]; const first_span_list = target.children[0];
assert.htmlEqualWithOptions(firstSpanList.innerHTML, expected, { withoutNormalizeHtml: true }); assert.htmlEqualWithOptions(first_span_list.innerHTML, expected, {
withoutNormalizeHtml: true
});
const secondSpanList = target.children[1]; const second_span_list = target.children[1];
assert.htmlEqualWithOptions(secondSpanList.innerHTML, expected, { withoutNormalizeHtml: true }); assert.htmlEqualWithOptions(second_span_list.innerHTML, expected, {
withoutNormalizeHtml: true
});
} }
}; };

@ -2,9 +2,9 @@ export default {
async test({ assert, target, window }) { async test({ assert, target, window }) {
const [btn1, btn2] = target.querySelectorAll('button'); const [btn1, btn2] = target.querySelectorAll('button');
const clickEvent = new window.MouseEvent('click'); const click_event = new window.MouseEvent('click');
await btn2.dispatchEvent(clickEvent); await btn2.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -17,7 +17,7 @@ export default {
` `
); );
await btn1.dispatchEvent(clickEvent); await btn1.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -30,7 +30,7 @@ export default {
` `
); );
await btn2.dispatchEvent(clickEvent); await btn2.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `
@ -43,7 +43,7 @@ export default {
` `
); );
await btn1.dispatchEvent(clickEvent); await btn1.dispatchEvent(click_event);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
` `

@ -0,0 +1,20 @@
export default {
html: `
<p style="background-color: green; font-size: 12px;"></p>
`,
test({ assert, target, window, component }) {
const p = target.querySelector('p');
const styles = window.getComputedStyle(p);
assert.equal(styles.backgroundColor, 'green');
assert.equal(styles.fontSize, '12px');
{
component.modify = true;
const p = target.querySelector('p');
const styles = window.getComputedStyle(p);
assert.equal(styles.backgroundColor, 'green');
assert.equal(styles.fontSize, '50px');
}
}
};

@ -0,0 +1,12 @@
<script>
let settings = {
fontSize: 12,
bg: 'green'
};
export let modify = false;
$: if (modify) {
settings.fontSize = 50;
}
</script>
<p style:font-size="{settings.fontSize}px" style="background-color: {settings.bg}" />

@ -4,14 +4,14 @@ export default {
<div>&nbsp;hello&nbsp; &nbsp;hello</div>`, <div>&nbsp;hello&nbsp; &nbsp;hello</div>`,
test({ assert, target }) { test({ assert, target }) {
const divList = target.querySelectorAll('div'); const div_list = target.querySelectorAll('div');
assert.equal(divList[0].textContent.charCodeAt(0), 160); assert.equal(div_list[0].textContent.charCodeAt(0), 160);
assert.equal(divList[1].textContent.charCodeAt(0), 160); assert.equal(div_list[1].textContent.charCodeAt(0), 160);
assert.equal(divList[1].textContent.charCodeAt(6), 160); assert.equal(div_list[1].textContent.charCodeAt(6), 160);
assert.equal(divList[1].textContent.charCodeAt(7), 160); assert.equal(div_list[1].textContent.charCodeAt(7), 160);
assert.equal(divList[2].textContent.charCodeAt(0), 160); assert.equal(div_list[2].textContent.charCodeAt(0), 160);
assert.equal(divList[2].textContent.charCodeAt(6), 160); assert.equal(div_list[2].textContent.charCodeAt(6), 160);
assert.equal(divList[2].textContent.charCodeAt(7), 32); //normal space assert.equal(div_list[2].textContent.charCodeAt(7), 32); //normal space
assert.equal(divList[2].textContent.charCodeAt(8), 160); assert.equal(div_list[2].textContent.charCodeAt(8), 160);
} }
}; };

@ -14,7 +14,7 @@ export default {
// it's okay not to remove the node during hydration // it's okay not to remove the node during hydration
// will not be seen by user anyway // will not be seen by user anyway
removeNoScript(target); remove_no_script(target);
assert.htmlEqual( assert.htmlEqual(
target.innerHTML, target.innerHTML,
@ -26,7 +26,7 @@ export default {
} }
}; };
function removeNoScript(target) { function remove_no_script(target) {
target.querySelectorAll('noscript').forEach((elem) => { target.querySelectorAll('noscript').forEach((elem) => {
elem.parentNode.removeChild(elem); elem.parentNode.removeChild(elem);
}); });

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save