Merge branch 'master' into pr/4877

pull/4877/head
Conduitry 5 years ago
commit e72b3f367e

@ -2,7 +2,6 @@
**/expected.js
_output
test/*/samples/*/output.js
node_modules
# automatically generated
internal_exports.ts

@ -1,65 +1,6 @@
module.exports = {
root: true,
rules: {
indent: 'off',
'no-unused-vars': 'off',
semi: [2, 'always'],
'keyword-spacing': [2, { before: true, after: true }],
'space-before-blocks': [2, 'always'],
'no-mixed-spaces-and-tabs': [2, 'smart-tabs'],
'no-cond-assign': 0,
'object-shorthand': [2, 'always'],
'no-const-assign': 2,
'no-class-assign': 2,
'no-this-before-super': 2,
'no-var': 2,
'no-unreachable': 2,
'valid-typeof': 2,
'quote-props': [2, 'as-needed'],
'one-var': [2, 'never'],
'prefer-arrow-callback': 2,
'prefer-const': [2, { destructuring: 'all' }],
'arrow-spacing': 2,
'no-inner-declarations': 0,
'require-atomic-updates': 'off',
'@typescript-eslint/indent': 'off',
'@typescript-eslint/camelcase': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/array-type': ['error', 'array-simple'],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-member-accessibility': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_'
}
],
'@typescript-eslint/no-object-literal-type-assertion': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/prefer-interface': 'off'
},
globals: {
globalThis: false
},
env: {
es6: true,
browser: true,
node: true,
mocha: true
},
extends: [
'eslint:recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'plugin:@typescript-eslint/recommended'
],
parserOptions: {
ecmaVersion: 9,
sourceType: 'module'
},
plugins: ['svelte3'],
extends: '@sveltejs',
settings: {
'import/core-modules': [
'svelte',
@ -69,20 +10,5 @@ module.exports = {
'estree'
],
'svelte3/compiler': require('./compiler')
},
overrides: [
{
files: ['*.js'],
rules: {
'@typescript-eslint/no-var-requires': 'off'
}
},
{
files: ['*.svelte'],
processor: 'svelte3/svelte3',
rules: {
'@typescript-eslint/indent': 'off'
}
}
]
}
};

@ -8,7 +8,6 @@ jobs:
node-version: [8, 10, 12, 14]
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
@ -24,7 +23,10 @@ jobs:
- uses: actions/setup-node@v1
- run: 'npm i && npm run lint'
Unit:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1

@ -2,11 +2,24 @@
## Unreleased
* Fix `bind:this` to the value of an `{#each}` block ([#4517](https://github.com/sveltejs/svelte/issues/4517))
* Fix reactivity when assigning to contextual `{#each}` variable ([#4574](https://github.com/sveltejs/svelte/issues/4574), [#4744](https://github.com/sveltejs/svelte/issues/4744))
* Fix binding to contextual `{#each}` values that shadow outer names ([#4757](https://github.com/sveltejs/svelte/issues/4757))
* Work around EdgeHTML DOM issue when removing attributes during hydration ([#4911](https://github.com/sveltejs/svelte/pull/4911))
* Throw CSS parser error when `:global()` does not contain a selector ([#4930](https://github.com/sveltejs/svelte/issues/4930))
## 3.23.0
* Update `<select>` with `bind:value` when the available `<option>`s change ([#1764](https://github.com/sveltejs/svelte/issues/1764))
* Add `muted` binding for media elements ([#2998](https://github.com/sveltejs/svelte/issues/2998))
* Fix inconsistencies when setting a two-way bound `<input>` to `undefined` ([#3569](https://github.com/sveltejs/svelte/issues/3569))
* Fix setting `<select multiple>` when there are spread attributes ([#4392](https://github.com/sveltejs/svelte/issues/4392))
* Fix let-less `<slot>` with context overflow ([#4624](https://github.com/sveltejs/svelte/issues/4624))
* Fix resize listening on certain older browsers ([#4752](https://github.com/sveltejs/svelte/issues/4752))
* Add `a11y-no-onchange` warning ([#4788](https://github.com/sveltejs/svelte/pull/4788))
* Add `muted` binding for media elements ([#2998](https://github.com/sveltejs/svelte/issues/2998))
* Fix let-less `<slot>` with context overflow ([#4624](https://github.com/sveltejs/svelte/issues/4624))
* Fix `use:` actions being recreated when a keyed `{#each}` is reordered ([#4693](https://github.com/sveltejs/svelte/issues/4693))
* Fix `{@html}` when using tags that can only appear inside certain tags ([#4852](https://github.com/sveltejs/svelte/issues/4852))
* Fix reactivity when binding directly to `{#each}` context ([#4879](https://github.com/sveltejs/svelte/issues/4879))
## 3.22.3

@ -34,11 +34,6 @@ npm install
> Do not use Yarn to install the dependencies, as the specific package versions in `package-lock.json` are used to build and test Svelte.
> Many tests depend on newlines being preserved as `<LF>`. On Windows, you can ensure this by cloning with:
> ```bash
> git -c core.autocrlf=false clone https://github.com/sveltejs/svelte.git
> ```
To build the compiler, and all the other modules included in the package:
```bash
@ -77,6 +72,9 @@ npm install && npm run update
npm run dev
```
### Is svelte.dev down?
Probably not, but it's possible. If you can't seem to access any `.dev` sites, check out [this SuperUser question and answer](https://superuser.com/q/1413402).
## License

1169
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.22.3",
"version": "3.23.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -63,18 +63,19 @@
"@rollup/plugin-sucrase": "^3.0.0",
"@rollup/plugin-typescript": "^2.0.1",
"@rollup/plugin-virtual": "^2.0.0",
"@sveltejs/eslint-config": "github:sveltejs/eslint-config#v0.0.1",
"@types/mocha": "^5.2.7",
"@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^3.0.2",
"@typescript-eslint/parser": "^3.0.2",
"acorn": "^7.1.0",
"agadoo": "^1.1.0",
"c8": "^5.0.1",
"code-red": "0.1.1",
"codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22",
"eslint": "^6.3.0",
"eslint-plugin-import": "^2.18.2",
"eslint": "^7.1.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^1.0.0",
"is-reference": "^1.1.4",

@ -121,6 +121,9 @@ An element or component can have multiple spread attributes, interspersed with r
<input {...$$restProps}>
```
> The `value` attribute of an `input` element or its children `option` elements must not be set with spread attributes when using `bind:group` or `bind:checked`. Svelte needs to be able to see the element's `value` directly in the markup in these cases so that it can link it to the bound variable.
---
### Text expressions
@ -598,18 +601,18 @@ Media elements (`<audio>` and `<video>`) have their own set of bindings — six
* `duration` (readonly) — the total duration of the video, in seconds
* `buffered` (readonly) — an array of `{start, end}` objects
* `seekable` (readonly) — ditto
* `played` (readonly) — ditto
* `seekable` (readonly) — ditto
* `seeking` (readonly) — boolean
* `ended` (readonly) — boolean
...and five *two-way* bindings:
* `currentTime` — the current point in the video, in seconds
* `playbackRate` — how fast to play the video, where 1 is 'normal'
* `currentTime` — the current playback time in the video, in seconds
* `playbackRate` — how fast or slow to play the video, where 1 is 'normal'
* `paused` — this one should be self-explanatory
* `volume` — a value between 0 and 1
* `muted` — a boolean value where true is muted
* `muted` — a boolean value where `true` is muted
Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
@ -618,11 +621,12 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
src={clip}
bind:duration
bind:buffered
bind:played
bind:seekable
bind:seeking
bind:played
bind:ended
bind:currentTime
bind:playbackRate
bind:paused
bind:volume
bind:muted

@ -287,7 +287,9 @@ The second argument to `readable` is the same as the second argument to `writabl
```js
import { readable } from 'svelte/store';
const time = readable(new Date(), set => {
const time = readable(null, set => {
set(new Date());
const interval = setInterval(() => {
set(new Date());
}, 1000);
@ -521,7 +523,7 @@ $: $size = big ? 100 : 10;
### `svelte/transition`
The `svelte/transition` module exports six functions: `fade`, `fly`, `slide`, `scale`, `draw` and `crossfade`. They are for use with Svelte [`transitions`](docs#transition_fn).
The `svelte/transition` module exports seven functions: `fade`, `blur`, `fly`, `slide`, `scale`, `draw` and `crossfade`. They are for use with Svelte [`transitions`](docs#transition_fn).
#### `fade`

@ -109,8 +109,8 @@
<div>
<video
poster="http://sveltejs.github.io/assets/caminandes-llamigos.jpg"
src="http://sveltejs.github.io/assets/caminandes-llamigos.mp4"
poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
on:mousemove={handleMousemove}
on:mousedown={handleMousedown}
bind:currentTime={time}

@ -1,5 +0,0 @@
---
question: Is svelte.dev down?
---
Probably not, but it's possible. If you can't seem to access any `.dev` sites, check out [this SuperUser question and answer](https://superuser.com/q/1413402).

@ -2,4 +2,10 @@
question: What about Typescript support?
---
You need to install a [community supported preprocessor](https://github.com/sveltejs/integrations#preprocessors) such as [svelte-preprocess](https://github.com/kaisermann/svelte-preprocess). Work is ongoing to improve [IDE support](https://github.com/sveltejs/language-tools/issues/83) and build [additional CLI tooling](https://github.com/sveltejs/language-tools/issues/68)
You need to install a [community supported preprocessor](https://github.com/sveltejs/integrations#preprocessors) such as [svelte-preprocess](https://github.com/kaisermann/svelte-preprocess). Work is ongoing to improve [IDE support](https://github.com/sveltejs/language-tools/issues/83). You can also 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 can use the following syntax:
```
let x: number;
$: x = count + 1;
```

@ -54,6 +54,33 @@
<Icon name="github"/>
</NavItem>
</Nav>
<div class="BLM">
<a
target="_blank"
rel="noopener noreferrer"
href="https://blacklivesmatter.com/"
>Black Lives Matter</a>.
Support
<a
target="_blank"
rel="noopener noreferrer"
href="https://www.joincampaignzero.org/"
>Campaign Zero</a>, the
<a
target="_blank"
rel="noopener noreferrer"
href="https://support.eji.org/give/153413/#!/donation/checkout"
>Equal&nbsp;Justice&nbsp;Initiative</a>, and
<a
target="_blank"
rel="noopener noreferrer"
href="https://bailfunds.github.io/"
>local bail funds</a>.
</div>
{/if}
<main>
@ -61,11 +88,47 @@
</main>
<style>
.BLM {
background-color: black;
color: white;
position: fixed;
bottom: 0;
width: 100vw;
text-align: center;
padding: 1em;
z-index: 999;
}
.BLM a {
white-space: nowrap;
}
:global(header){
filter: grayscale(100%) /* BLM */
}
main {
position: relative;
margin: 0 auto;
/* padding: var(--nav-h) var(--side-nav) 0 var(--side-nav); */
padding: var(--nav-h) 0 0 0;
padding-bottom: 64px; /* BLM */
overflow-x: hidden;
}
@media screen and (max-width: 769px) {
main {
padding-bottom: 90px; /* BLM */
}
}
@media screen and (max-width: 377px) {
.BLM {
font-size: 85%;
}
main {
padding-bottom: 75px; /* BLM */
}
}
main > :global(*) {
filter: grayscale(100%) /* BLM */
}
</style>

@ -60,7 +60,7 @@ export default class Binding extends Node {
scope.dependencies_for_name.get(name).forEach(name => {
const variable = component.var_lookup.get(name);
if (variable) {
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
variable.mutated = true;
}
});
} else {

@ -14,6 +14,8 @@ import { invalidate } from '../../render_dom/invalidate';
import { Node, FunctionExpression, Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces';
import { is_reserved_keyword } from '../../utils/reserved_keywords';
import replace_object from '../../utils/replace_object';
import EachBlock from '../EachBlock';
type Owner = Wrapper | TemplateNode;
@ -134,6 +136,8 @@ export default class Expression {
const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
});
const each_block = template_scope.get_owner(name);
(each_block as EachBlock).has_binding = true;
} else {
component.add_reference(name);
@ -311,6 +315,10 @@ export default class Expression {
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
const object_name = get_object(assignee).name;
if (scope.has(object_name)) return;
// normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones
@ -327,6 +335,23 @@ export default class Expression {
}
});
const context = block.bindings.get(object_name);
if (context) {
// for `{#each array as item}`
// replace `item = 1` to `each_array[each_index] = 1`, this allow us to mutate the array
// rather than mutating the local `item` variable
const { snippet, object, property } = context;
const replaced: any = replace_object(assignee, snippet);
if (node.type === 'AssignmentExpression') {
node.left = replaced;
} else {
node.argument = replaced;
}
contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name);
}
this.replace(invalidate(block.renderer, scope, node, traced));
}
}

@ -299,7 +299,7 @@ export default class Block {
${this.chunks.mount}
}`;
} else {
properties.mount = x`function #mount(#target, #anchor, #remount) {
properties.mount = x`function #mount(#target, #anchor) {
${this.chunks.mount}
}`;
}
@ -452,6 +452,9 @@ export default class Block {
render_listeners(chunk: string = '') {
if (this.event_listeners.length > 0) {
this.add_variable({ type: 'Identifier', name: '#mounted' });
this.chunks.destroy.push(b`#mounted = false`);
const dispose: Identifier = {
type: 'Identifier',
name: `#dispose${chunk}`
@ -462,8 +465,10 @@ export default class Block {
if (this.event_listeners.length === 1) {
this.chunks.mount.push(
b`
if (#remount) ${dispose}();
${dispose} = ${this.event_listeners[0]};
if (!#mounted) {
${dispose} = ${this.event_listeners[0]};
#mounted = true;
}
`
);
@ -472,10 +477,12 @@ export default class Block {
);
} else {
this.chunks.mount.push(b`
if (#remount) @run_all(${dispose});
${dispose} = [
${this.event_listeners}
];
if (!#mounted) {
${dispose} = [
${this.event_listeners}
];
#mounted = true;
}
`);
this.chunks.destroy.push(

@ -26,6 +26,7 @@ export default class Renderer {
options: CompileOptions;
context: ContextMember[] = [];
initial_context: ContextMember[] = [];
context_lookup: Map<string, ContextMember> = new Map();
context_overflow: boolean;
blocks: Array<Block | Node | Node[]> = [];
@ -110,8 +111,12 @@ export default class Renderer {
// these determine whether variable is included in initial context
// array, so must have the highest priority
if (variable.export_name) member.priority += 8;
if (variable.referenced) member.priority += 16;
if (variable.export_name) member.priority += 16;
if (variable.referenced) member.priority += 32;
} else if (member.is_non_contextual) {
// determine whether variable is included in initial context
// array, so must have the highest priority
member.priority += 8;
}
if (!member.is_contextual) {
@ -121,6 +126,17 @@ export default class Renderer {
this.context.sort((a, b) => (b.priority - a.priority) || ((a.index.value as number) - (b.index.value as number)));
this.context.forEach((member, i) => member.index.value = i);
let i = this.context.length;
while (i--) {
const member = this.context[i];
if (member.variable) {
if (member.variable.referenced || member.variable.export_name) break;
} else if (member.is_non_contextual) {
break;
}
}
this.initial_context = this.context.slice(0, i + 1);
}
add_to_context(name: string, contextual = false) {

@ -301,24 +301,13 @@ export default function dom(
const instance_javascript = component.extract_javascript(component.ast.instance);
let i = renderer.context.length;
while (i--) {
const member = renderer.context[i];
if (member.variable) {
if (member.variable.referenced || member.variable.export_name) break;
} else if (member.is_non_contextual) {
break;
}
}
const initial_context = renderer.context.slice(0, i + 1);
const has_definition = (
component.compile_options.dev ||
(instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 ||
uses_props ||
component.partly_hoisted.length > 0 ||
initial_context.length > 0 ||
renderer.initial_context.length > 0 ||
component.reactive_declarations.length > 0 ||
capture_state ||
inject_state
@ -404,7 +393,7 @@ export default function dom(
const return_value = {
type: 'ArrayExpression',
elements: initial_context.map(member => ({
elements: renderer.initial_context.map(member => ({
type: 'Identifier',
name: member.name
}) as Expression)

@ -102,25 +102,12 @@ export default class AttributeWrapper {
} else if (is_select_value_attribute) {
// annoying special case
const is_multiple_select = element.node.get_static_attribute_value('multiple');
const i = block.get_unique_name('i');
const option = block.get_unique_name('option');
const if_statement = is_multiple_select
? b`
${option}.selected = ~${last}.indexOf(${option}.__value);`
: b`
if (${option}.__value === ${last}) {
${option}.selected = true;
${{ type: 'BreakStatement' }};
}`; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
updater = b`
for (var ${i} = 0; ${i} < ${element.var}.options.length; ${i} += 1) {
var ${option} = ${element.var}.options[${i}];
${if_statement}
}
`;
if (is_multiple_select) {
updater = b`@select_options(${element.var}, ${last});`;
} else {
updater = b`@select_option(${element.var}, ${last});`;
}
block.chunks.mount.push(b`
${last} = ${value};

@ -1,7 +1,9 @@
import { b, x } from 'code-red';
import Binding from '../../../nodes/Binding';
import ElementWrapper from '../Element';
import InlineComponentWrapper from '../InlineComponent';
import get_object from '../../../utils/get_object';
import replace_object from '../../../utils/replace_object';
import Block from '../../Block';
import Renderer from '../../Renderer';
import flatten_reference from '../../../utils/flatten_reference';
@ -10,20 +12,20 @@ import { Node, Identifier } from 'estree';
export default class BindingWrapper {
node: Binding;
parent: ElementWrapper;
parent: ElementWrapper | InlineComponentWrapper;
object: string;
handler: {
uses_context: boolean;
mutation: (Node | Node[]);
contextual_dependencies: Set<string>;
snippet?: Node;
lhs?: Node;
};
snippet: Node;
is_readonly: boolean;
needs_lock: boolean;
constructor(block: Block, node: Binding, parent: ElementWrapper) {
constructor(block: Block, node: Binding, parent: ElementWrapper | InlineComponentWrapper) {
this.node = node;
this.parent = parent;
@ -33,7 +35,7 @@ export default class BindingWrapper {
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
if (parent.node.name === 'select') {
parent.select_binding_dependencies = dependencies;
(parent as ElementWrapper).select_binding_dependencies = dependencies;
dependencies.forEach((prop: string) => {
parent.renderer.component.indirect_dependencies.set(prop, new Set());
});
@ -87,7 +89,7 @@ export default class BindingWrapper {
const update_conditions: any[] = this.needs_lock ? [x`!${lock}`] : [];
const mount_conditions: any[] = [];
const dependency_array = [...this.node.expression.dependencies];
const dependency_array = Array.from(this.get_dependencies());
if (dependency_array.length > 0) {
update_conditions.push(block.renderer.dirty(dependency_array));
@ -207,7 +209,7 @@ export default class BindingWrapper {
}
function get_dom_updater(
element: ElementWrapper,
element: ElementWrapper | InlineComponentWrapper,
binding: BindingWrapper
) {
const { node } = element;
@ -270,21 +272,17 @@ function get_event_handler(
contextual_dependencies: Set<string>;
lhs?: Node;
} {
const value = get_value_from_dom(renderer, binding.parent, binding);
const contextual_dependencies = new Set(binding.node.expression.contextual_dependencies);
const contextual_dependencies = new Set<string>(binding.node.expression.contextual_dependencies);
const context = block.bindings.get(name);
let set_store;
if (context) {
const { object, property, modifier, store } = context;
if (lhs.type === 'Identifier') {
lhs = modifier(x`${object}[${property}]`);
contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name);
}
const { object, property, store, snippet } = context;
lhs = replace_object(lhs, snippet);
contextual_dependencies.add(object.name);
contextual_dependencies.add(property.name);
contextual_dependencies.delete(name);
if (store) {
set_store = b`${store}.set(${`$${store}`});`;
@ -297,6 +295,8 @@ function get_event_handler(
}
}
const value = get_value_from_dom(renderer, binding.parent, binding);
const mutation = b`
${lhs} = ${value};
${set_store}
@ -305,20 +305,21 @@ function get_event_handler(
return {
uses_context: binding.node.is_contextual || binding.node.expression.uses_context, // TODO this is messy
mutation,
contextual_dependencies
contextual_dependencies,
lhs,
};
}
function get_value_from_dom(
renderer: Renderer,
element: ElementWrapper,
element: ElementWrapper | InlineComponentWrapper,
binding: BindingWrapper
) {
const { node } = element;
const { name } = binding.node;
if (name === 'this') {
return x`$$node`;
return x`$$value`;
}
// <select bind:value='selected>

@ -27,6 +27,11 @@ import EventHandler from './EventHandler';
import { extract_names } from 'periscopic';
import Action from '../../../nodes/Action';
interface BindingGroup {
events: string[];
bindings: Binding[];
}
const events = [
{
event_names: ['input'],
@ -405,7 +410,7 @@ export default class ElementWrapper extends Wrapper {
get_render_statement(block: Block) {
const { name, namespace } = this.node;
if (namespace === 'http://www.w3.org/2000/svg') {
if (namespace === namespaces.svg) {
return x`@svg_element("${name}")`;
}
@ -436,14 +441,9 @@ export default class ElementWrapper extends Wrapper {
}
add_directives_in_order (block: Block) {
interface BindingGroup {
events: string[];
bindings: Binding[];
}
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action;
const bindingGroups = events
const binding_groups = events
.map(event => ({
events: event.event_names,
bindings: this.bindings
@ -467,7 +467,7 @@ export default class ElementWrapper extends Wrapper {
}
([
...bindingGroups,
...binding_groups,
...this.event_handlers,
this_binding,
...this.node.actions
@ -487,144 +487,141 @@ export default class ElementWrapper extends Wrapper {
});
}
add_bindings(block: Block, bindingGroup) {
add_bindings(block: Block, binding_group: BindingGroup) {
const { renderer } = this;
if (bindingGroup.bindings.length === 0) return;
if (binding_group.bindings.length === 0) return;
renderer.component.has_reactive_assignments = true;
const lock = bindingGroup.bindings.some(binding => binding.needs_lock) ?
const lock = binding_group.bindings.some(binding => binding.needs_lock) ?
block.get_unique_name(`${this.var.name}_updating`) :
null;
if (lock) block.add_variable(lock, x`false`);
[bindingGroup].forEach(group => {
const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`);
renderer.add_to_context(handler.name);
const handler = renderer.component.get_unique_name(`${this.var.name}_${binding_group.events.join('_')}_handler`);
renderer.add_to_context(handler.name);
// TODO figure out how to handle locks
const needs_lock = group.bindings.some(binding => binding.needs_lock);
// TODO figure out how to handle locks
const needs_lock = binding_group.bindings.some(binding => binding.needs_lock);
const dependencies: Set<string> = new Set();
const contextual_dependencies: Set<string> = new Set();
const dependencies: Set<string> = new Set();
const contextual_dependencies: Set<string> = new Set();
group.bindings.forEach(binding => {
// TODO this is a mess
add_to_set(dependencies, binding.get_dependencies());
add_to_set(contextual_dependencies, binding.node.expression.contextual_dependencies);
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);
binding_group.bindings.forEach(binding => {
// TODO this is a mess
add_to_set(dependencies, binding.get_dependencies());
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);
binding.render(block, lock);
});
binding.render(block, lock);
});
// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// own hands
let animation_frame;
if (group.events[0] === 'timeupdate') {
animation_frame = block.get_unique_name(`${this.var.name}_animationframe`);
block.add_variable(animation_frame);
}
// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// own hands
let animation_frame;
if (binding_group.events[0] === 'timeupdate') {
animation_frame = block.get_unique_name(`${this.var.name}_animationframe`);
block.add_variable(animation_frame);
}
const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame;
const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame;
let callee = renderer.reference(handler);
let callee = renderer.reference(handler);
// TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) {
const args = Array.from(contextual_dependencies).map(name => renderer.reference(name));
// TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) {
const args = Array.from(contextual_dependencies).map(name => renderer.reference(name));
// need to create a block-local function that calls an instance-level function
if (animation_frame) {
block.chunks.init.push(b`
function ${handler}() {
@_cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) {
${animation_frame} = @raf(${handler});
${needs_lock && b`${lock} = true;`}
}
${callee}.call(${this.var}, ${args});
}
`);
} else {
block.chunks.init.push(b`
function ${handler}() {
// need to create a block-local function that calls an instance-level function
if (animation_frame) {
block.chunks.init.push(b`
function ${handler}() {
@_cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) {
${animation_frame} = @raf(${handler});
${needs_lock && b`${lock} = true;`}
${callee}.call(${this.var}, ${args});
}
`);
}
callee = handler;
${callee}.call(${this.var}, ${args});
}
`);
} else {
block.chunks.init.push(b`
function ${handler}() {
${needs_lock && b`${lock} = true;`}
${callee}.call(${this.var}, ${args});
}
`);
}
const params = Array.from(contextual_dependencies).map(name => ({
type: 'Identifier',
name
}));
this.renderer.component.partly_hoisted.push(b`
function ${handler}(${params}) {
${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.invalidate(dep)};`)}
}
`);
group.events.forEach(name => {
if (name === 'elementresize') {
// special case
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener);
block.chunks.mount.push(
b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
);
callee = handler;
}
block.chunks.destroy.push(
b`${resize_listener}();`
);
} else {
block.event_listeners.push(
x`@listen(${this.var}, "${name}", ${callee})`
);
}
});
const params = Array.from(contextual_dependencies).map(name => ({
type: 'Identifier',
name
}));
this.renderer.component.partly_hoisted.push(b`
function ${handler}(${params}) {
${binding_group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.invalidate(dep)};`)}
}
`);
const some_initial_state_is_undefined = group.bindings
.map(binding => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
const should_initialise = (
this.node.name === 'select' ||
group.bindings.find(binding => {
return (
binding.node.name === 'indeterminate' ||
binding.node.name === 'textContent' ||
binding.node.name === 'innerHTML' ||
binding.is_readonly_media_attribute()
);
})
);
binding_group.events.forEach(name => {
if (name === 'elementresize') {
// special case
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener);
if (should_initialise) {
const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`;
block.chunks.hydrate.push(
b`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
block.chunks.mount.push(
b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
);
}
if (group.events[0] === 'elementresize') {
block.chunks.hydrate.push(
b`@add_render_callback(() => ${callee}.call(${this.var}));`
block.chunks.destroy.push(
b`${resize_listener}();`
);
} else {
block.event_listeners.push(
x`@listen(${this.var}, "${name}", ${callee})`
);
}
});
const some_initial_state_is_undefined = binding_group.bindings
.map(binding => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
const should_initialise = (
this.node.name === 'select' ||
binding_group.bindings.find(binding => {
return (
binding.node.name === 'indeterminate' ||
binding.node.name === 'textContent' ||
binding.node.name === 'innerHTML' ||
binding.is_readonly_media_attribute()
);
})
);
if (should_initialise) {
const callback = has_local_function ? handler : x`() => ${callee}.call(${this.var})`;
block.chunks.hydrate.push(
b`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
);
}
if (binding_group.events[0] === 'elementresize') {
block.chunks.hydrate.push(
b`@add_render_callback(() => ${callee}.call(${this.var}));`
);
}
if (lock) {
block.chunks.update.push(b`${lock} = false;`);
}
@ -635,7 +632,7 @@ export default class ElementWrapper extends Wrapper {
renderer.component.has_reactive_assignments = true;
const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var);
const binding_callback = bind_this(renderer.component, block, this_binding, this.var);
block.chunks.mount.push(binding_callback);
}
@ -705,10 +702,27 @@ export default class ElementWrapper extends Wrapper {
);
block.chunks.update.push(b`
${fn}(${this.var}, @get_spread_update(${levels}, [
${fn}(${this.var}, ${data} = @get_spread_update(${levels}, [
${updates}
]));
`);
// handle edge cases for elements
if (this.node.name === 'select') {
const dependencies = new Set();
for (const attr of this.attributes) {
for (const dep of attr.node.dependencies) {
dependencies.add(dep);
}
}
block.chunks.mount.push(b`
if (${data}.multiple) @select_options(${this.var}, ${data}.value);
`);
block.chunks.update.push(b`
if (${block.renderer.dirty(Array.from(dependencies))} && ${data}.multiple) @select_options(${this.var}, ${data}.value);
`);
}
}
add_transitions(

@ -1,4 +1,5 @@
import Wrapper from '../shared/Wrapper';
import BindingWrapper from '../Element/Binding';
import Renderer from '../../Renderer';
import Block from '../../Block';
import InlineComponent from '../../../nodes/InlineComponent';
@ -309,7 +310,7 @@ export default class InlineComponentWrapper extends Wrapper {
component.has_reactive_assignments = true;
if (binding.name === 'this') {
return bind_this(component, block, binding, this.var);
return bind_this(component, block, new BindingWrapper(block, binding, this), this.var);
}
const id = component.get_unique_name(`${this.var.name}_${binding.name}_binding`);

@ -53,8 +53,8 @@ export default class RawMustacheTagWrapper extends Tag {
const update_anchor = in_head ? 'null' : needs_anchor ? html_anchor : this.next ? this.next.var : 'null';
block.chunks.hydrate.push(b`${html_tag} = new @HtmlTag(${init}, ${update_anchor});`);
block.chunks.mount.push(b`${html_tag}.m(${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`);
block.chunks.hydrate.push(b`${html_tag} = new @HtmlTag(${update_anchor});`);
block.chunks.mount.push(b`${html_tag}.m(${init}, ${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`);
if (needs_anchor) {
block.add_element(html_anchor, x`@empty()`, x`@empty()`, parent_node);

@ -1,50 +1,33 @@
import flatten_reference from '../../../utils/flatten_reference';
import { b, x } from 'code-red';
import Component from '../../../Component';
import Block from '../../Block';
import Binding from '../../../nodes/Binding';
import BindingWrapper from '../Element/Binding';
import { Identifier } from 'estree';
export default function bind_this(component: Component, block: Block, binding: Binding, variable: Identifier) {
export default function bind_this(component: Component, block: Block, binding: BindingWrapper, variable: Identifier) {
const fn = component.get_unique_name(`${variable.name}_binding`);
block.renderer.add_to_context(fn.name);
const callee = block.renderer.reference(fn.name);
let lhs;
let object;
let body;
if (binding.is_contextual && binding.raw_expression.type === 'Identifier') {
// bind:x={y} — we can't just do `y = x`, we need to
// to `array[index] = x;
const { name } = binding.raw_expression;
const { snippet } = block.bindings.get(name);
lhs = snippet;
body = b`${lhs} = $$value`; // TODO we need to invalidate... something
} else {
object = flatten_reference(binding.raw_expression).name;
lhs = binding.raw_expression;
body = binding.raw_expression.type === 'Identifier'
? b`
${block.renderer.invalidate(object, x`${lhs} = $$value`)};
`
: b`
${lhs} = $$value;
${block.renderer.invalidate(object)};
`;
}
const contextual_dependencies: Identifier[] = Array.from(binding.expression.contextual_dependencies).map(name => ({
type: 'Identifier',
name
}));
if (contextual_dependencies.length) {
const { contextual_dependencies, mutation, lhs } = binding.handler;
const dependencies = binding.get_dependencies();
const body = b`
${mutation}
${Array.from(dependencies)
.filter(dep => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep))
.map(dep => b`${block.renderer.invalidate(dep)};`)}
`;
if (contextual_dependencies.size) {
const params: Identifier[] = Array.from(contextual_dependencies).map(name => ({
type: 'Identifier',
name
}));
component.partly_hoisted.push(b`
function ${fn}($$value, ${contextual_dependencies}) {
function ${fn}($$value, ${params}) {
if (${lhs} === $$value) return;
@binding_callbacks[$$value ? 'unshift' : 'push'](() => {
${body}
@ -53,7 +36,7 @@ export default function bind_this(component: Component, block: Block, binding: B
`);
const args = [];
for (const id of contextual_dependencies) {
for (const id of params) {
args.push(id);
if (block.variables.has(id.name)) {
if (block.renderer.context_lookup.get(id.name).is_contextual) continue;
@ -69,7 +52,7 @@ export default function bind_this(component: Component, block: Block, binding: B
const ${unassign} = () => ${callee}(null, ${args});
`);
const condition = Array.from(contextual_dependencies)
const condition = Array.from(params)
.map(name => x`${name} !== ${block.renderer.reference(name.name)}`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);

@ -1,5 +1,6 @@
// https://github.com/darkskyapp/string-hash/blob/master/index.js
export default function hash(str: string): string {
str = str.replace(/\r/g, "");
let hash = 5381;
let i = str.length;

@ -0,0 +1,14 @@
import { Node } from 'estree';
export default function replace_object(node: Node, replacement: Node) {
if (node.type === 'Identifier') return replacement;
const ancestor = node;
let parent;
while (node.type === 'MemberExpression') {
parent = node;
node = node.object;
}
parent.object = replacement;
return ancestor;
}

@ -54,6 +54,13 @@ export default function read_style(parser: Parser, start: number, attributes: No
}, node.start);
}
if (node.type === 'PseudoClassSelector' && node.name === 'global' && node.children === null) {
parser.error({
code: `css-syntax-error`,
message: `:global() must contain a selector`
}, node.loc.start.offset);
}
if (node.loc) {
node.start = node.loc.start.offset;
node.end = node.loc.end.offset;

@ -19,7 +19,6 @@ interface Fragment {
/* outro */ o: (local: any) => void;
/* destroy */ d: (detaching: 0|1) => void;
}
// eslint-disable-next-line @typescript-eslint/class-name-casing
interface T$$ {
dirty: number[];
ctx: null|any;

@ -158,14 +158,16 @@ export function claim_element(nodes, name, attributes, svg) {
const node = nodes[i];
if (node.nodeName === name) {
let j = 0;
const remove = [];
while (j < node.attributes.length) {
const attribute = node.attributes[j];
if (attributes[attribute.name]) {
j++;
} else {
node.removeAttribute(attribute.name);
const attribute = node.attributes[j++];
if (!attributes[attribute.name]) {
remove.push(attribute.name);
}
}
for (let k = 0; k < remove.length; k++) {
node.removeAttribute(remove[k]);
}
return nodes.splice(i, 1)[0];
}
}
@ -322,29 +324,36 @@ export class HtmlTag {
t: HTMLElement;
a: HTMLElement;
constructor(html: string, anchor: HTMLElement = null) {
this.e = element('div');
constructor(anchor: HTMLElement = null) {
this.a = anchor;
this.u(html);
this.e = this.n = null;
}
m(target: HTMLElement, anchor: HTMLElement = null) {
for (let i = 0; i < this.n.length; i += 1) {
insert(target, this.n[i], anchor);
m(html: string, target: HTMLElement, anchor: HTMLElement = null) {
if (!this.e) {
this.e = element(target.nodeName as keyof HTMLElementTagNameMap);
this.t = target;
this.h(html);
}
this.t = target;
this.i(anchor);
}
u(html: string) {
h(html) {
this.e.innerHTML = html;
this.n = Array.from(this.e.childNodes);
}
i(anchor) {
for (let i = 0; i < this.n.length; i += 1) {
insert(this.t, this.n[i], anchor);
}
}
p(html: string) {
this.d();
this.u(html);
this.m(this.t, this.a);
this.h(html);
this.i(this.a);
}
d() {

@ -56,7 +56,7 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
function insert(block) {
transition_in(block, 1);
block.m(node, next, lookup.has(block.key));
block.m(node, next);
lookup.set(block.key, block);
next = block.first;
n--;

@ -56,7 +56,8 @@ describe('css', () => {
const config = try_require(`./samples/${dir}/_config.js`) || {};
const input = fs
.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8')
.replace(/\s+$/, '');
.replace(/\s+$/, '')
.replace(/\r/g, '');
const expected_warnings = (config.warnings || []).map(normalize_warning);

@ -63,7 +63,7 @@ describe('custom-elements', function() {
const warnings = [];
(solo ? it.only : skip ? it.skip : it)(dir, async () => {
const config = loadConfig(`./custom-elements/samples/${dir}/_config.js`);
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const expected_warnings = config.warnings || [];
const bundle = await rollup({
@ -82,7 +82,7 @@ describe('custom-elements', function() {
transform(code, id) {
if (id.endsWith('.svelte')) {
const compiled = svelte.compile(code, {
const compiled = svelte.compile(code.replace(/\r/g, ""), {
customElement: true,
dev: config.dev
});

@ -25,7 +25,7 @@ describe("js", () => {
(solo ? it.only : it)(dir, () => {
const config = loadConfig(`${resolved}/_config.js`);
const input = fs.readFileSync(`${resolved}/input.svelte`, "utf-8").replace(/\s+$/, "");
const input = fs.readFileSync(`${resolved}/input.svelte`, "utf-8").replace(/\s+$/, "").replace(/\r/g, "");
let actual;
@ -56,8 +56,8 @@ describe("js", () => {
try {
assert.equal(
actual.trim().replace(/^[ \t]+$/gm, ""),
expected.trim().replace(/^[ \t]+$/gm, "")
actual.trim().replace(/^[ \t]+$/gm, "").replace(/\r/g, ""),
expected.trim().replace(/^[ \t]+$/gm, "").replace(/\r/g, "")
);
} catch (error) {
if (shouldUpdateExpected()) {

@ -14,6 +14,7 @@ import {
function create_fragment(ctx) {
let button;
let foo_action;
let mounted;
let dispose;
return {
@ -21,10 +22,13 @@ function create_fragment(ctx) {
button = element("button");
button.textContent = "foo";
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, button, anchor);
if (remount) dispose();
dispose = action_destroyer(foo_action = foo.call(null, button, /*foo_function*/ ctx[1]));
if (!mounted) {
dispose = action_destroyer(foo_action = foo.call(null, button, /*foo_function*/ ctx[1]));
mounted = true;
}
},
p(ctx, [dirty]) {
if (foo_action && is_function(foo_action.update) && dirty & /*bar*/ 1) foo_action.update.call(null, /*foo_function*/ ctx[1]);
@ -33,6 +37,7 @@ function create_fragment(ctx) {
o: noop,
d(detaching) {
if (detaching) detach(button);
mounted = false;
dispose();
}
};

@ -14,6 +14,7 @@ import {
function create_fragment(ctx) {
let a;
let link_action;
let mounted;
let dispose;
return {
@ -22,16 +23,20 @@ function create_fragment(ctx) {
a.textContent = "Test";
attr(a, "href", "#");
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, a, anchor);
if (remount) dispose();
dispose = action_destroyer(link_action = link.call(null, a));
if (!mounted) {
dispose = action_destroyer(link_action = link.call(null, a));
mounted = true;
}
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(a);
mounted = false;
dispose();
}
};

@ -10,23 +10,27 @@ import {
} from "svelte/internal";
function create_fragment(ctx) {
let mounted;
let dispose;
add_render_callback(/*onlinestatuschanged*/ ctx[1]);
return {
c: noop,
m(target, anchor, remount) {
if (remount) run_all(dispose);
dispose = [
listen(window, "online", /*onlinestatuschanged*/ ctx[1]),
listen(window, "offline", /*onlinestatuschanged*/ ctx[1])
];
m(target, anchor) {
if (!mounted) {
dispose = [
listen(window, "online", /*onlinestatuschanged*/ ctx[1]),
listen(window, "offline", /*onlinestatuschanged*/ ctx[1])
];
mounted = true;
}
},
p: noop,
i: noop,
o: noop,
d(detaching) {
mounted = false;
run_all(dispose);
}
};

@ -12,6 +12,7 @@ import {
function create_fragment(ctx) {
let details;
let mounted;
let dispose;
return {
@ -21,11 +22,14 @@ function create_fragment(ctx) {
details.innerHTML = `<summary>summary</summary>content
`;
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, details, anchor);
details.open = /*open*/ ctx[0];
if (remount) dispose();
dispose = listen(details, "toggle", /*details_toggle_handler*/ ctx[1]);
if (!mounted) {
dispose = listen(details, "toggle", /*details_toggle_handler*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*open*/ 1) {
@ -36,6 +40,7 @@ function create_fragment(ctx) {
o: noop,
d(detaching) {
if (detaching) detach(details);
mounted = false;
dispose();
}
};

@ -17,6 +17,7 @@ function create_fragment(ctx) {
let input0;
let t;
let input1;
let mounted;
let dispose;
return {
@ -27,16 +28,19 @@ function create_fragment(ctx) {
attr(input0, "type", "file");
attr(input1, "type", "file");
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, input0, anchor);
insert(target, t, anchor);
insert(target, input1, anchor);
if (remount) run_all(dispose);
dispose = [
listen(input0, "change", /*input0_change_handler*/ ctx[1]),
listen(input1, "change", /*input1_change_handler*/ ctx[2])
];
if (!mounted) {
dispose = [
listen(input0, "change", /*input0_change_handler*/ ctx[1]),
listen(input1, "change", /*input1_change_handler*/ ctx[2])
];
mounted = true;
}
},
p: noop,
i: noop,
@ -45,6 +49,7 @@ function create_fragment(ctx) {
if (detaching) detach(input0);
if (detaching) detach(t);
if (detaching) detach(input1);
mounted = false;
run_all(dispose);
}
};

@ -20,6 +20,7 @@ function create_fragment(ctx) {
let t0;
let t1;
let input;
let mounted;
let dispose;
return {
@ -29,14 +30,17 @@ function create_fragment(ctx) {
t1 = space();
input = element("input");
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, p, anchor);
append(p, t0);
insert(target, t1, anchor);
insert(target, input, anchor);
set_input_value(input, /*foo*/ ctx[0]);
if (remount) dispose();
dispose = listen(input, "input", /*input_input_handler*/ ctx[1]);
if (!mounted) {
dispose = listen(input, "input", /*input_input_handler*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*foo*/ 1) set_data(t0, /*foo*/ ctx[0]);
@ -51,6 +55,7 @@ function create_fragment(ctx) {
if (detaching) detach(p);
if (detaching) detach(t1);
if (detaching) detach(input);
mounted = false;
dispose();
}
};

@ -24,6 +24,7 @@ function create_fragment(ctx) {
let t1;
let input;
let current;
let mounted;
let dispose;
const foo = new Foo({ props: { x: y } });
const bar = new Bar({ props: { x: /*z*/ ctx[0] } });
@ -36,7 +37,7 @@ function create_fragment(ctx) {
t1 = space();
input = element("input");
},
m(target, anchor, remount) {
m(target, anchor) {
mount_component(foo, target, anchor);
insert(target, t0, anchor);
mount_component(bar, target, anchor);
@ -44,8 +45,11 @@ function create_fragment(ctx) {
insert(target, input, anchor);
set_input_value(input, /*z*/ ctx[0]);
current = true;
if (remount) dispose();
dispose = listen(input, "input", /*input_input_handler*/ ctx[1]);
if (!mounted) {
dispose = listen(input, "input", /*input_input_handler*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
const bar_changes = {};
@ -73,6 +77,7 @@ function create_fragment(ctx) {
destroy_component(bar, detaching);
if (detaching) detach(t1);
if (detaching) detach(input);
mounted = false;
dispose();
}
};

@ -22,6 +22,7 @@ function create_fragment(ctx) {
let t0;
let t1;
let button;
let mounted;
let dispose;
return {
@ -32,13 +33,16 @@ function create_fragment(ctx) {
button = element("button");
button.textContent = "reset";
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, h1, anchor);
append(h1, t0);
insert(target, t1, anchor);
insert(target, button, anchor);
if (remount) dispose();
dispose = listen(button, "click", /*click_handler*/ ctx[2]);
if (!mounted) {
dispose = listen(button, "click", /*click_handler*/ ctx[2]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*$foo*/ 2) set_data(t0, /*$foo*/ ctx[1]);
@ -49,6 +53,7 @@ function create_fragment(ctx) {
if (detaching) detach(h1);
if (detaching) detach(t1);
if (detaching) detach(button);
mounted = false;
dispose();
}
};

@ -12,22 +12,27 @@ import {
function create_fragment(ctx) {
let input;
let mounted;
let dispose;
return {
c() {
input = element("input");
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, input, anchor);
if (remount) dispose();
dispose = listen(input, "input", make_uppercase);
if (!mounted) {
dispose = listen(input, "input", make_uppercase);
mounted = true;
}
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(input);
mounted = false;
dispose();
}
};

@ -53,7 +53,7 @@ function create_each_block(ctx) {
t5 = text(" ago:");
t6 = space();
attr(span, "class", "meta");
html_tag = new HtmlTag(raw_value, null);
html_tag = new HtmlTag(null);
attr(div, "class", "comment");
},
m(target, anchor) {
@ -67,7 +67,7 @@ function create_each_block(ctx) {
append(span, t4);
append(span, t5);
append(div, t6);
html_tag.m(div);
html_tag.m(raw_value, div);
},
p(ctx, dirty) {
if (dirty & /*comments*/ 1 && t2_value !== (t2_value = /*comment*/ ctx[4].author + "")) set_data(t2, t2_value);

@ -26,6 +26,7 @@ function create_fragment(ctx) {
let t4;
let t5;
let button2;
let mounted;
let dispose;
return {
@ -43,7 +44,7 @@ function create_fragment(ctx) {
button2 = element("button");
button2.textContent = "click";
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, p0, anchor);
append(p0, button0);
append(p0, t1);
@ -53,15 +54,18 @@ function create_fragment(ctx) {
append(p1, t4);
insert(target, t5, anchor);
insert(target, button2, anchor);
if (remount) run_all(dispose);
dispose = [
listen(button0, "click", /*updateHandler1*/ ctx[2]),
listen(button1, "click", /*updateHandler2*/ ctx[3]),
listen(button2, "click", function () {
if (is_function(/*clickHandler*/ ctx[0])) /*clickHandler*/ ctx[0].apply(this, arguments);
})
];
if (!mounted) {
dispose = [
listen(button0, "click", /*updateHandler1*/ ctx[2]),
listen(button1, "click", /*updateHandler2*/ ctx[3]),
listen(button2, "click", function () {
if (is_function(/*clickHandler*/ ctx[0])) /*clickHandler*/ ctx[0].apply(this, arguments);
})
];
mounted = true;
}
},
p(new_ctx, [dirty]) {
ctx = new_ctx;
@ -75,6 +79,7 @@ function create_fragment(ctx) {
if (detaching) detach(p1);
if (detaching) detach(t5);
if (detaching) detach(button2);
mounted = false;
run_all(dispose);
}
};

@ -13,6 +13,7 @@ import {
function create_fragment(ctx) {
let a;
let mounted;
let dispose;
return {
@ -21,16 +22,20 @@ function create_fragment(ctx) {
a.textContent = "this should not navigate to example.com";
attr(a, "href", "https://example.com");
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, a, anchor);
if (remount) dispose();
dispose = listen(a, "touchstart", touchstart_handler);
if (!mounted) {
dispose = listen(a, "touchstart", touchstart_handler);
mounted = true;
}
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(a);
mounted = false;
dispose();
}
};

@ -22,6 +22,7 @@ function create_fragment(ctx) {
let button1;
let t3;
let button2;
let mounted;
let dispose;
return {
@ -36,27 +37,31 @@ function create_fragment(ctx) {
button2 = element("button");
button2.textContent = "or me!";
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, div, anchor);
append(div, button0);
append(div, t1);
append(div, button1);
append(div, t3);
append(div, button2);
if (remount) run_all(dispose);
dispose = [
listen(button0, "click", stop_propagation(prevent_default(handleClick))),
listen(button1, "click", handleClick, { once: true, capture: true }),
listen(button2, "click", handleClick, true),
listen(div, "touchstart", handleTouchstart, { passive: true })
];
if (!mounted) {
dispose = [
listen(button0, "click", stop_propagation(prevent_default(handleClick))),
listen(button1, "click", handleClick, { once: true, capture: true }),
listen(button2, "click", handleClick, true),
listen(div, "touchstart", handleTouchstart, { passive: true })
];
mounted = true;
}
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(div);
mounted = false;
run_all(dispose);
}
};

@ -0,0 +1,59 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
detach,
element,
init,
insert,
listen,
noop,
safe_not_equal
} from "svelte/internal";
function create_fragment(ctx) {
let button;
let mounted;
let dispose;
return {
c() {
button = element("button");
},
m(target, anchor) {
insert(target, button, anchor);
if (!mounted) {
dispose = listen(button, "click", /*click_handler*/ ctx[1]);
mounted = true;
}
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(button);
mounted = false;
dispose();
}
};
}
function instance($$self, $$props, $$invalidate) {
let foo;
function unreferenced() {
$$invalidate(0, foo = 1);
}
const click_handler = () => $$invalidate(0, foo = 2);
return [foo, click_handler];
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default Component;

@ -0,0 +1,7 @@
<script>
let foo;
function unreferenced () {
foo = 1;
}
</script>
<button on:click={() => foo = 2}></button>

@ -13,6 +13,7 @@ import {
function create_fragment(ctx) {
let input;
let mounted;
let dispose;
return {
@ -21,16 +22,20 @@ function create_fragment(ctx) {
attr(input, "type", "file");
input.multiple = true;
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, input, anchor);
if (remount) dispose();
dispose = listen(input, "change", /*input_change_handler*/ ctx[1]);
if (!mounted) {
dispose = listen(input, "change", /*input_change_handler*/ ctx[1]);
mounted = true;
}
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(input);
mounted = false;
dispose();
}
};

@ -20,6 +20,7 @@ function create_fragment(ctx) {
let input;
let t0;
let button;
let mounted;
let dispose;
return {
@ -32,18 +33,21 @@ function create_fragment(ctx) {
attr(input, "type", "text");
input.required = true;
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, form, anchor);
append(form, input);
set_input_value(input, /*test*/ ctx[0]);
append(form, t0);
append(form, button);
if (remount) run_all(dispose);
dispose = [
listen(input, "input", /*input_input_handler*/ ctx[2]),
listen(form, "submit", /*handleSubmit*/ ctx[1])
];
if (!mounted) {
dispose = [
listen(input, "input", /*input_input_handler*/ ctx[2]),
listen(form, "submit", /*handleSubmit*/ ctx[1])
];
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*test*/ 1 && input.value !== /*test*/ ctx[0]) {
@ -54,6 +58,7 @@ function create_fragment(ctx) {
o: noop,
d(detaching) {
if (detaching) detach(form);
mounted = false;
run_all(dispose);
}
};

@ -16,6 +16,7 @@ import {
function create_fragment(ctx) {
let input;
let mounted;
let dispose;
return {
@ -23,15 +24,18 @@ function create_fragment(ctx) {
input = element("input");
attr(input, "type", "range");
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, input, anchor);
set_input_value(input, /*value*/ ctx[0]);
if (remount) run_all(dispose);
dispose = [
listen(input, "change", /*input_change_input_handler*/ ctx[1]),
listen(input, "input", /*input_change_input_handler*/ ctx[1])
];
if (!mounted) {
dispose = [
listen(input, "change", /*input_change_input_handler*/ ctx[1]),
listen(input, "input", /*input_change_input_handler*/ ctx[1])
];
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*value*/ 1) {
@ -42,6 +46,7 @@ function create_fragment(ctx) {
o: noop,
d(detaching) {
if (detaching) detach(input);
mounted = false;
run_all(dispose);
}
};

@ -20,6 +20,7 @@ function create_fragment(ctx) {
let h1;
let t1;
let t2;
let mounted;
let dispose;
return {
@ -31,14 +32,17 @@ function create_fragment(ctx) {
t2 = text("!");
input.value = /*name*/ ctx[0];
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, input, anchor);
insert(target, t0, anchor);
insert(target, h1, anchor);
append(h1, t1);
append(h1, t2);
if (remount) dispose();
dispose = listen(input, "input", /*onInput*/ ctx[1]);
if (!mounted) {
dispose = listen(input, "input", /*onInput*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*name*/ 1 && input.value !== /*name*/ ctx[0]) {
@ -53,6 +57,7 @@ function create_fragment(ctx) {
if (detaching) detach(input);
if (detaching) detach(t0);
if (detaching) detach(h1);
mounted = false;
dispose();
}
};

@ -13,6 +13,7 @@ import {
function create_fragment(ctx) {
let input;
let mounted;
let dispose;
return {
@ -20,11 +21,14 @@ function create_fragment(ctx) {
input = element("input");
attr(input, "type", "checkbox");
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, input, anchor);
input.checked = /*foo*/ ctx[0];
if (remount) dispose();
dispose = listen(input, "change", /*input_change_handler*/ ctx[1]);
if (!mounted) {
dispose = listen(input, "change", /*input_change_handler*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*foo*/ 1) {
@ -35,6 +39,7 @@ function create_fragment(ctx) {
o: noop,
d(detaching) {
if (detaching) detach(input);
mounted = false;
dispose();
}
};

@ -20,6 +20,7 @@ function create_fragment(ctx) {
let p;
let t2;
let t3;
let mounted;
let dispose;
return {
@ -31,14 +32,17 @@ function create_fragment(ctx) {
t2 = text("x: ");
t3 = text(/*x*/ ctx[0]);
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, button, anchor);
insert(target, t1, anchor);
insert(target, p, anchor);
append(p, t2);
append(p, t3);
if (remount) dispose();
dispose = listen(button, "click", /*foo*/ ctx[1]);
if (!mounted) {
dispose = listen(button, "click", /*foo*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*x*/ 1) set_data(t3, /*x*/ ctx[0]);
@ -49,6 +53,7 @@ function create_fragment(ctx) {
if (detaching) detach(button);
if (detaching) detach(t1);
if (detaching) detach(p);
mounted = false;
dispose();
}
};

@ -21,6 +21,7 @@ function create_fragment(ctx) {
let t2;
let t3_value = /*things*/ ctx[0].length + "";
let t3;
let mounted;
let dispose;
return {
@ -32,14 +33,17 @@ function create_fragment(ctx) {
t2 = text("number of things: ");
t3 = text(t3_value);
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, button, anchor);
insert(target, t1, anchor);
insert(target, p, anchor);
append(p, t2);
append(p, t3);
if (remount) dispose();
dispose = listen(button, "click", /*foo*/ ctx[1]);
if (!mounted) {
dispose = listen(button, "click", /*foo*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*things*/ 1 && t3_value !== (t3_value = /*things*/ ctx[0].length + "")) set_data(t3, t3_value);
@ -50,6 +54,7 @@ function create_fragment(ctx) {
if (detaching) detach(button);
if (detaching) detach(t1);
if (detaching) detach(p);
mounted = false;
dispose();
}
};

@ -20,6 +20,7 @@ function create_fragment(ctx) {
let p;
let t2;
let t3;
let mounted;
let dispose;
return {
@ -31,14 +32,17 @@ function create_fragment(ctx) {
t2 = text("x: ");
t3 = text(/*x*/ ctx[0]);
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, button, anchor);
insert(target, t1, anchor);
insert(target, p, anchor);
append(p, t2);
append(p, t3);
if (remount) dispose();
dispose = listen(button, "click", /*click_handler*/ ctx[1]);
if (!mounted) {
dispose = listen(button, "click", /*click_handler*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*x*/ 1) set_data(t3, /*x*/ ctx[0]);
@ -49,6 +53,7 @@ function create_fragment(ctx) {
if (detaching) detach(button);
if (detaching) detach(t1);
if (detaching) detach(p);
mounted = false;
dispose();
}
};

@ -21,6 +21,7 @@ function create_fragment(ctx) {
let t2;
let t3_value = /*things*/ ctx[0].length + "";
let t3;
let mounted;
let dispose;
return {
@ -32,14 +33,17 @@ function create_fragment(ctx) {
t2 = text("number of things: ");
t3 = text(t3_value);
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, button, anchor);
insert(target, t1, anchor);
insert(target, p, anchor);
append(p, t2);
append(p, t3);
if (remount) dispose();
dispose = listen(button, "click", /*click_handler*/ ctx[1]);
if (!mounted) {
dispose = listen(button, "click", /*click_handler*/ ctx[1]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*things*/ 1 && t3_value !== (t3_value = /*things*/ ctx[0].length + "")) set_data(t3, t3_value);
@ -50,6 +54,7 @@ function create_fragment(ctx) {
if (detaching) detach(button);
if (detaching) detach(t1);
if (detaching) detach(p);
mounted = false;
dispose();
}
};

@ -116,7 +116,8 @@ function instance($$self, $$props, $$invalidate) {
function div_binding($$value) {
binding_callbacks[$$value ? "unshift" : "push"](() => {
$$invalidate(0, node = $$value);
node = $$value;
$$invalidate(0, node);
});
}

@ -19,6 +19,7 @@ function create_fragment(ctx) {
let audio_updating = false;
let audio_animationframe;
let audio_is_paused = true;
let mounted;
let dispose;
function audio_timeupdate_handler() {
@ -42,7 +43,7 @@ function create_fragment(ctx) {
if (/*seeking*/ ctx[9] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[18].call(audio));
if (/*ended*/ ctx[10] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[19].call(audio));
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, audio, anchor);
if (!isNaN(/*volume*/ ctx[6])) {
@ -55,21 +56,23 @@ function create_fragment(ctx) {
audio.playbackRate = /*playbackRate*/ ctx[8];
}
if (remount) run_all(dispose);
dispose = [
listen(audio, "progress", /*audio_progress_handler*/ ctx[11]),
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[12]),
listen(audio, "timeupdate", audio_timeupdate_handler),
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[14]),
listen(audio, "play", /*audio_play_pause_handler*/ ctx[15]),
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[15]),
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[16]),
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[17]),
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[18]),
listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[18]),
listen(audio, "ended", /*audio_ended_handler*/ ctx[19])
];
if (!mounted) {
dispose = [
listen(audio, "progress", /*audio_progress_handler*/ ctx[11]),
listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[12]),
listen(audio, "timeupdate", audio_timeupdate_handler),
listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[14]),
listen(audio, "play", /*audio_play_pause_handler*/ ctx[15]),
listen(audio, "pause", /*audio_play_pause_handler*/ ctx[15]),
listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[16]),
listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[17]),
listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[18]),
listen(audio, "seeked", /*audio_seeking_seeked_handler*/ ctx[18]),
listen(audio, "ended", /*audio_ended_handler*/ ctx[19])
];
mounted = true;
}
},
p(ctx, [dirty]) {
if (!audio_updating && dirty & /*currentTime*/ 8 && !isNaN(/*currentTime*/ ctx[3])) {
@ -98,6 +101,7 @@ function create_fragment(ctx) {
o: noop,
d(detaching) {
if (detaching) detach(audio);
mounted = false;
run_all(dispose);
}
};

@ -7,7 +7,8 @@ import {
init,
insert,
noop,
safe_not_equal
safe_not_equal,
select_option
} from "svelte/internal";
function create_fragment(ctx) {
@ -33,26 +34,11 @@ function create_fragment(ctx) {
append(select, option0);
append(select, option1);
select_value_value = /*current*/ ctx[0];
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
if (option.__value === select_value_value) {
option.selected = true;
break;
}
}
select_option(select, select_value_value);
},
p(ctx, [dirty]) {
if (dirty & /*current*/ 1 && select_value_value !== (select_value_value = /*current*/ ctx[0])) {
for (var i = 0; i < select.options.length; i += 1) {
var option = select.options[i];
if (option.__value === select_value_value) {
option.selected = true;
break;
}
}
select_option(select, select_value_value);
}
},
i: noop,

@ -19,6 +19,7 @@ function create_fragment(ctx) {
let video_updating = false;
let video_animationframe;
let video_resize_listener;
let mounted;
let dispose;
function video_timeupdate_handler() {
@ -38,15 +39,18 @@ function create_fragment(ctx) {
if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[5].call(video));
add_render_callback(() => /*video_elementresize_handler*/ ctx[6].call(video));
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, video, anchor);
video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[6].bind(video));
if (remount) run_all(dispose);
dispose = [
listen(video, "timeupdate", video_timeupdate_handler),
listen(video, "resize", /*video_resize_handler*/ ctx[5])
];
if (!mounted) {
dispose = [
listen(video, "timeupdate", video_timeupdate_handler),
listen(video, "resize", /*video_resize_handler*/ ctx[5])
];
mounted = true;
}
},
p(ctx, [dirty]) {
if (!video_updating && dirty & /*currentTime*/ 1 && !isNaN(/*currentTime*/ ctx[0])) {
@ -60,6 +64,7 @@ function create_fragment(ctx) {
d(detaching) {
if (detaching) detach(video);
video_resize_listener();
mounted = false;
run_all(dispose);
}
};

@ -10,23 +10,27 @@ import {
} from "svelte/internal";
function create_fragment(ctx) {
let mounted;
let dispose;
add_render_callback(/*onlinestatuschanged*/ ctx[1]);
return {
c: noop,
m(target, anchor, remount) {
if (remount) run_all(dispose);
dispose = [
listen(window, "online", /*onlinestatuschanged*/ ctx[1]),
listen(window, "offline", /*onlinestatuschanged*/ ctx[1])
];
m(target, anchor) {
if (!mounted) {
dispose = [
listen(window, "online", /*onlinestatuschanged*/ ctx[1]),
listen(window, "offline", /*onlinestatuschanged*/ ctx[1])
];
mounted = true;
}
},
p: noop,
i: noop,
o: noop,
d(detaching) {
mounted = false;
run_all(dispose);
}
};

@ -25,6 +25,7 @@ function create_fragment(ctx) {
let p;
let t0;
let t1;
let mounted;
let dispose;
add_render_callback(/*onwindowscroll*/ ctx[1]);
@ -34,18 +35,21 @@ function create_fragment(ctx) {
t0 = text("scrolled to ");
t1 = text(/*y*/ ctx[0]);
},
m(target, anchor, remount) {
m(target, anchor) {
insert(target, p, anchor);
append(p, t0);
append(p, t1);
if (remount) dispose();
dispose = listen(window, "scroll", () => {
scrolling = true;
clearTimeout(scrolling_timeout);
scrolling_timeout = setTimeout(clear_scrolling, 100);
/*onwindowscroll*/ ctx[1]();
});
if (!mounted) {
dispose = listen(window, "scroll", () => {
scrolling = true;
clearTimeout(scrolling_timeout);
scrolling_timeout = setTimeout(clear_scrolling, 100);
/*onwindowscroll*/ ctx[1]();
});
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*y*/ 1 && !scrolling) {
@ -61,6 +65,7 @@ function create_fragment(ctx) {
o: noop,
d(detaching) {
if (detaching) detach(p);
mounted = false;
dispose();
}
};

@ -20,7 +20,7 @@ describe('parse', () => {
(skip ? it.skip : solo ? it.only : it)(dir, () => {
const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`) || {};
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8').replace(/\s+$/, '');
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8').replace(/\s+$/, '').replace(/\r/g, "");
const expectedOutput = tryToLoadJson(`${__dirname}/samples/${dir}/output.json`);
const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`);
@ -38,13 +38,9 @@ describe('parse', () => {
} catch (err) {
if (err.name !== 'ParseError') throw err;
if (!expectedError) throw err;
const { code, message, pos, start } = err
try {
assert.equal(err.code, expectedError.code);
assert.equal(err.message, expectedError.message);
assert.deepEqual(err.start, expectedError.start);
assert.equal(err.pos, expectedError.pos);
assert.equal(err.toString().split('\n')[0], `${expectedError.message} (${expectedError.start.line}:${expectedError.start.column})`);
assert.deepEqual({ code, message, pos, start }, expectedError);
} catch (err2) {
const e = err2.code === 'MODULE_NOT_FOUND' ? err : err2;
throw e;

@ -0,0 +1,10 @@
{
"code": "css-syntax-error",
"message": ":global() must contain a selector",
"start": {
"line": 2,
"column": 1,
"character": 9
},
"pos": 9
}

@ -39,7 +39,7 @@ describe("runtime", () => {
filename
}, compileOptions);
const { js: { code } } = compile(fs.readFileSync(filename, "utf-8"), options);
const { js: { code } } = compile(fs.readFileSync(filename, "utf-8").replace(/\r/g, ""), options);
return module._compile(code, filename);
};
@ -103,7 +103,7 @@ describe("runtime", () => {
try {
const { js } = compile(
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
fs.readFileSync(`${cwd}/${file}`, 'utf-8').replace(/\r/g, ""),
{
...compileOptions,
filename: file

@ -0,0 +1,34 @@
export default {
props: {
items: [],
selected: 'two'
},
html: `
<select></select>
<p>selected: two</p>
`,
ssrHtml: `
<select value="two"></select>
<p>selected: two</p>
`,
test({ assert, component, target }) {
component.items = [ 'one', 'two', 'three' ];
const options = target.querySelectorAll('option');
assert.ok(!options[0].selected);
assert.ok(options[1].selected);
assert.ok(!options[2].selected);
assert.htmlEqual(target.innerHTML, `
<select>
<option value='one'>one</option>
<option value='two'>two</option>
<option value='three'>three</option>
</select>
<p>selected: two</p>
`);
}
};

@ -0,0 +1,12 @@
<script>
export let selected;
export let items;
</script>
<select bind:value={selected}>
{#each items as item}
<option>{item}</option>
{/each}
</select>
<p>selected: {selected || 'nothing'}</p>

@ -0,0 +1,5 @@
<script>
export let action;
</script>
<div use:action />

@ -0,0 +1,21 @@
export default {
test({ assert, component, raf }) {
assert.equal(component.count, 0);
component.arr = ["2"];
assert.equal(component.count, 1);
component.arr = ["1", "2"];
assert.equal(component.count, 2);
component.arr = ["2", "1"];
assert.equal(component.count, 2);
component.arr = [];
assert.equal(component.count, 0);
},
};

@ -0,0 +1,17 @@
<script>
import Component from "./Component.svelte";
export let arr = [];
export let count = 0;
function action(node, params) {
count += 1;
return {
destroy() {
count -= 1;
}
};
}
</script>
{#each arr as item (item)}
<Component {action} />
{/each}

@ -0,0 +1,23 @@
export default {
html: `
Hello
<input />
`,
ssrHtml: `
Hello
<input value="Hello"/>
`,
async test({ assert, target, window }) {
const input = target.querySelector("input");
input.value = "abcd";
await input.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
abcd
<input />
`
);
},
};

@ -0,0 +1,10 @@
<script>
let a = [
{ a: 'Hello' }
];
</script>
{#each a as { a }}
{a}
<input bind:value={a} />
{/each}

@ -0,0 +1,105 @@
export default {
html: `
<div>
Hello World
<input />
<input />
</div>
<div>
Sapper App
<input />
<input />
</div>
`,
ssrHtml: `
<div>
Hello World
<input value="Hello"/>
<input value="World"/>
</div>
<div>
Sapper App
<input value="Sapper"/>
<input value="App"/>
</div>
`,
async test({ assert, target, window }) {
const [input1, input2, input3, input4] = target.querySelectorAll("input");
input1.value = "Awesome";
await input1.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
Awesome World
<input />
<input />
</div>
<div>
Sapper App
<input />
<input />
</div>
`
);
input2.value = "Svelte";
await input2.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
Awesome Svelte
<input />
<input />
</div>
<div>
Sapper App
<input />
<input />
</div>
`
);
input3.value = "Foo";
await input3.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
Awesome Svelte
<input />
<input />
</div>
<div>
Foo App
<input />
<input />
</div>
`
);
input4.value = "Bar";
await input4.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
Awesome Svelte
<input />
<input />
</div>
<div>
Foo Bar
<input />
<input />
</div>
`
);
},
};

@ -0,0 +1,14 @@
<script>
let a = [
['Hello', 'World'],
['Sapper', 'App'],
]
</script>
{#each a as a}
<div>
{a[0]} {a[1]}
<input bind:value={a[0]}>
<input bind:value={a[1]}>
</div>
{/each}

@ -0,0 +1,64 @@
export default {
html: `
<div>
b: Hello
<input />
</div>
<button>Button</button>
`,
ssrHtml: `
<div>
b: Hello
<input value="Hello" />
</div>
<button>Button</button>
`,
async test({ assert, target, window }) {
const input = target.querySelector("input");
const button = target.querySelector("button");
input.value = "Awesome";
await input.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
b: Awesome
<input />
</div>
<button>Button</button>
`
);
await button.dispatchEvent(new window.MouseEvent("click"));
assert.htmlEqual(
target.innerHTML,
`
<div>
c: World
<input />
</div>
<button>Button</button>
`
);
assert.equal(input.value, 'World');
input.value = "Svelte";
await input.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
<div>
c: Svelte
<input />
</div>
<button>Button</button>
`
);
},
};

@ -0,0 +1,14 @@
<script>
let a = [
{ a: { b: 'Hello', c: 'World' }, key: 'b' },
];
</script>
{#each a as { a, key }}
<div>
{key}: {a[key]}
<input bind:value={a[key]}>
</div>
{/each}
<button on:click={() => a[0].key = 'c'}>Button</button>

@ -0,0 +1,23 @@
export default {
html: `
Hello
<input />
`,
ssrHtml: `
Hello
<input value="Hello"/>
`,
async test({ assert, target, window }) {
const input = target.querySelector("input");
input.value = "abcd";
await input.dispatchEvent(new window.Event("input"));
assert.htmlEqual(
target.innerHTML,
`
abcd
<input />
`
);
},
};

@ -0,0 +1,10 @@
<script>
let a = [
'Hello'
];
</script>
{#each a as a}
{a}
<input bind:value={a} />
{/each}

@ -0,0 +1,20 @@
export default {
html: `
<span class="content">foo</span>
<button>Test</button>
`,
async test({ assert, component, target, window }) {
const button = target.querySelector("button");
const clickEvent = new window.MouseEvent("click");
await button.dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<span class="content">bar</span>
<button>Test</button>
`
);
},
};

@ -0,0 +1,12 @@
<script>
let obj = {
prop: "foo"
};
let arr = [obj]
</script>
{#each arr as o}
<span class="content">{o.prop}</span>
<button on:click={ () => o = { ...o, prop: "bar" } }>Test</button>
{/each}

@ -0,0 +1,97 @@
export default {
html: `
<button>Add</button>
<span class="content">1</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">3</span>
<button>Test</button>
`,
async test({ assert, component, target, window }) {
let [incrementBtn, ...buttons] = target.querySelectorAll("button");
const clickEvent = new window.MouseEvent("click");
await buttons[0].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">3</span>
<button>Test</button>
`
);
await buttons[0].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">3</span>
<button>Test</button>
`
);
await buttons[2].dispatchEvent(clickEvent);
await buttons[2].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">12</span>
<button>Test</button>
`
);
await incrementBtn.dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">12</span>
<button>Test</button>
<span class="content">4</span>
<button>Test</button>
`
);
[incrementBtn, ...buttons] = target.querySelectorAll("button");
await buttons[3].dispatchEvent(clickEvent);
assert.htmlEqual(
target.innerHTML,
`
<button>Add</button>
<span class="content">4</span>
<button>Test</button>
<span class="content">2</span>
<button>Test</button>
<span class="content">12</span>
<button>Test</button>
<span class="content">8</span>
<button>Test</button>
`
);
},
};

@ -0,0 +1,13 @@
<script>
let obj = {
prop: "foo"
};
let arr = [1, 2, 3]
</script>
<button on:click={() => arr = [...arr, arr.length + 1]}>Add</button>
{#each arr as o}
<span class="content">{o}</span>
<button on:click={() => { o *= 2; }}>Test</button>
{/each}

@ -0,0 +1,18 @@
export default {
props: {
raw: "<tr><td>1</td><td>2</td></tr>",
},
html: `
<table>
<tbody>
<tr>
<td>5</td><td>7</td>
</tr>
<tr>
<td>1</td><td>2</td>
</tr>
</tbody>
</table>
`,
};

@ -0,0 +1,12 @@
<script>
export let raw;
</script>
<table>
<tbody>
<tr>
<td>5</td><td>7</td>
</tr>
{@html raw}
</tbody>
</table>

@ -0,0 +1,27 @@
let value;
let called = 0;
function callback(_value) {
called ++;
value = _value;
}
export default {
props: {
callback,
},
async test({ assert, component, target, window }) {
assert.equal(called, 1);
const input = target.querySelector('input');
const event = new window.Event('input');
input.value = 'h';
await input.dispatchEvent(event);
assert.equal(called, 2);
assert.equal(value.length, 3);
assert.equal(value[0], 'h');
assert.equal(value[1], '2');
assert.equal(value[2], '3');
}
};

@ -0,0 +1,10 @@
<script>
const refs = ['1','2','3']
export let callback = () => {};
$: callback(refs);
</script>
{#each refs as ref}
<input bind:value={ref} />
{/each}

@ -0,0 +1,39 @@
export default {
async test({ assert, component, target, window }) {
const [input1, input2] = target.querySelectorAll('input');
const select = target.querySelector('select');
const [option1, option2] = select.childNodes;
let selections = Array.from(select.selectedOptions);
assert.equal(selections.length, 2);
assert.ok(selections.includes(option1));
assert.ok(selections.includes(option2));
const event = new window.Event('change');
input1.checked = false;
await input1.dispatchEvent(event);
selections = Array.from(select.selectedOptions);
assert.equal(selections.length, 1);
assert.ok(!selections.includes(option1));
assert.ok(selections.includes(option2));
input2.checked = false;
await input2.dispatchEvent(event);
input1.checked = true;
await input1.dispatchEvent(event);
selections = Array.from(select.selectedOptions);
assert.equal(selections.length, 1);
assert.ok(selections.includes(option1));
assert.ok(!selections.includes(option2));
component.spread = { value: ['Hello', 'World'] };
selections = Array.from(select.selectedOptions);
assert.equal(selections.length, 2);
assert.ok(selections.includes(option1));
assert.ok(selections.includes(option2));
}
};

@ -0,0 +1,12 @@
<script>
let value = ['Hello', 'World'];
export let spread = {};
</script>
<select multiple {value} {...spread}>
<option>Hello</option>
<option>World</option>
</select>
<input type="checkbox" value="Hello" bind:group={value}>
<input type="checkbox" value="World" bind:group={value}>

@ -88,8 +88,8 @@ describe("ssr", () => {
try {
assert.equal(
css.code.replace(/^\s+/gm, ""),
expectedCss.replace(/^\s+/gm, "")
css.code.replace(/^\s+/gm, "").replace(/[\r\n]/g, ""),
expectedCss.replace(/^\s+/gm, "").replace(/[\r\n]/g, "")
);
} catch (error) {
if (shouldUpdateExpected()) {

@ -17,7 +17,7 @@ describe("validate", () => {
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, "utf-8").replace(/\s+$/, "");
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, "utf-8").replace(/\s+$/, "").replace(/\r/g, "");
const expected_warnings = tryToLoadJson(`${__dirname}/samples/${dir}/warnings.json`) || [];
const expected_errors = tryToLoadJson(`${__dirname}/samples/${dir}/errors.json`);
const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`);

Loading…
Cancel
Save