Merge branch 'master' into a11y-autocomplete-valid

pull/8520/head
Simon Holthausen 3 years ago
commit d2dba3248f

@ -1,3 +1,7 @@
# HEADS UP: BIG RESTRUCTURING UNDERWAY
The Svelte repo is currently in the process of heavy restructuring for Svelte 4. After that, work on Svelte 5 will likely change a lot on the compiler aswell. For that reason, please don't open PRs that are large in scope, touch more than a couple of files etc. In other words, bug fixes are fine, but feature PRs will likely not be merged.
### Before submitting the PR, please make sure you do the following ### Before submitting the PR, please make sure you do the following
- [ ] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [ ] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [ ] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`. - [ ] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.

@ -62,6 +62,8 @@ When [opening a new issue](https://github.com/sveltejs/svelte/issues/new/choose)
## Pull requests ## Pull requests
> HEADS UP: The Svelte repo is currently in the process of heavy restructuring for Svelte 4. After that, work on Svelte 5 will likely change a lot on the compiler aswell. For that reason, please don't open PRs that are large in scope, touch more than a couple of files etc. In other words, bug fixes are fine, but feature PRs will likely not be merged.
### Proposing a change ### Proposing a change
If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with [feature template](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml). If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with [feature template](https://github.com/sveltejs/svelte/issues/new?template=feature_request.yml).

@ -1078,11 +1078,17 @@ export interface SvelteMediaTimeRange {
end: number; end: number;
} }
export interface SvelteDocumentAttributes extends HTMLAttributes<Document> {
readonly 'bind:fullscreenElement'?: Document['fullscreenElement'] | undefined | null;
readonly 'bind:visibilityState'?: Document['visibilityState'] | undefined | null;
}
export interface SvelteWindowAttributes extends HTMLAttributes<Window> { export interface SvelteWindowAttributes extends HTMLAttributes<Window> {
readonly 'bind:innerWidth'?: Window['innerWidth'] | undefined | null; readonly 'bind:innerWidth'?: Window['innerWidth'] | undefined | null;
readonly 'bind:innerHeight'?: Window['innerHeight'] | undefined | null; readonly 'bind:innerHeight'?: Window['innerHeight'] | undefined | null;
readonly 'bind:outerWidth'?: Window['outerWidth'] | undefined | null; readonly 'bind:outerWidth'?: Window['outerWidth'] | undefined | null;
readonly 'bind:outerHeight'?: Window['outerHeight'] | undefined | null; readonly 'bind:outerHeight'?: Window['outerHeight'] | undefined | null;
readonly 'bind:devicePixelRatio'?: Window['devicePixelRatio'] | undefined | null;
'bind:scrollX'?: Window['scrollX'] | undefined | null; 'bind:scrollX'?: Window['scrollX'] | undefined | null;
'bind:scrollY'?: Window['scrollY'] | undefined | null; 'bind:scrollY'?: Window['scrollY'] | undefined | null;
readonly 'bind:online'?: Window['navigator']['onLine'] | undefined | null; readonly 'bind:online'?: Window['navigator']['onLine'] | undefined | null;
@ -1591,7 +1597,7 @@ export interface SvelteHTMLElements {
// Svelte specific // Svelte specific
'svelte:window': SvelteWindowAttributes; 'svelte:window': SvelteWindowAttributes;
'svelte:document': HTMLAttributes<Document>; 'svelte:document': SvelteDocumentAttributes;
'svelte:body': HTMLAttributes<HTMLElement>; 'svelte:body': HTMLAttributes<HTMLElement>;
'svelte:fragment': { slot?: string }; 'svelte:fragment': { slot?: string };
'svelte:options': { [name: string]: any }; 'svelte:options': { [name: string]: any };

@ -823,6 +823,8 @@ Inputs that work together can use `bind:group`.
<input type="checkbox" bind:group={fillings} value="Guac (extra)"> <input type="checkbox" bind:group={fillings} value="Guac (extra)">
``` ```
> `bind:group` only works if the inputs are in the same Svelte component.
#### bind:this #### bind:this
```sv ```sv
@ -1742,6 +1744,7 @@ You can also bind to the following properties:
* `scrollX` * `scrollX`
* `scrollY` * `scrollY`
* `online` — an alias for `window.navigator.onLine` * `online` — an alias for `window.navigator.onLine`
* `devicePixelRatio`
All except `scrollX` and `scrollY` are readonly. All except `scrollX` and `scrollY` are readonly.
@ -1756,6 +1759,9 @@ All except `scrollX` and `scrollY` are readonly.
```sv ```sv
<svelte:document on:event={handler}/> <svelte:document on:event={handler}/>
``` ```
```sv
<svelte:document bind:prop={value}/>
```
--- ---
@ -1770,6 +1776,15 @@ As with `<svelte:window>`, this element may only appear the top level of your co
/> />
``` ```
---
You can also bind to the following properties:
* `fullscreenElement`
* `visibilityState`
All are readonly.
### `<svelte:body>` ### `<svelte:body>`
```sv ```sv

@ -25,7 +25,7 @@ import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch'; import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object'; import get_object from './utils/get_object';
import Slot from './nodes/Slot'; import Slot from './nodes/Slot';
import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, ExpressionStatement, AssignmentExpression, Literal, Property, RestElement, ExportDefaultDeclaration, ExportAllDeclaration, FunctionDeclaration, FunctionExpression } from 'estree'; import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, ExpressionStatement, AssignmentExpression, Literal, Property, RestElement, ExportDefaultDeclaration, ExportAllDeclaration, FunctionDeclaration, FunctionExpression, Pattern, Expression } from 'estree';
import add_to_set from './utils/add_to_set'; import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles'; import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, b } from 'code-red'; import { print, b } from 'code-red';
@ -1034,7 +1034,7 @@ export default class Component {
const inserts = []; const inserts = [];
const props = []; const props = [];
function add_new_props(exported, local, default_value) { function add_new_props(exported: Identifier, local: Pattern, default_value: Expression) {
props.push({ props.push({
type: 'Property', type: 'Property',
method: false, method: false,
@ -1064,7 +1064,7 @@ export default class Component {
for (let index = 0; index < node.declarations.length; index++) { for (let index = 0; index < node.declarations.length; index++) {
const declarator = node.declarations[index]; const declarator = node.declarations[index];
if (declarator.id.type !== 'Identifier') { if (declarator.id.type !== 'Identifier') {
function get_new_name(local) { function get_new_name(local: Identifier): Identifier {
const variable = component.var_lookup.get(local.name); const variable = component.var_lookup.get(local.name);
if (variable.subscribable) { if (variable.subscribable) {
inserts.push(get_insert(variable)); inserts.push(get_insert(variable));
@ -1078,7 +1078,7 @@ export default class Component {
return local; return local;
} }
function rename_identifiers(param: Node) { function rename_identifiers(param: Pattern) {
switch (param.type) { switch (param.type) {
case 'ObjectPattern': { case 'ObjectPattern': {
const handle_prop = (prop: Property | RestElement) => { const handle_prop = (prop: Property | RestElement) => {
@ -1087,7 +1087,7 @@ export default class Component {
} else if (prop.value.type === 'Identifier') { } else if (prop.value.type === 'Identifier') {
prop.value = get_new_name(prop.value); prop.value = get_new_name(prop.value);
} else { } else {
rename_identifiers(prop.value); rename_identifiers(prop.value as Pattern);
} }
}; };
@ -1095,7 +1095,7 @@ export default class Component {
break; break;
} }
case 'ArrayPattern': { case 'ArrayPattern': {
const handle_element = (element: Node, index: number, array: Node[]) => { const handle_element = (element: Pattern | null, index: number, array: Array<Pattern | null>) => {
if (element) { if (element) {
if (element.type === 'Identifier') { if (element.type === 'Identifier') {
array[index] = get_new_name(element); array[index] = get_new_name(element);
@ -1110,7 +1110,11 @@ export default class Component {
} }
case 'RestElement': case 'RestElement':
param.argument = get_new_name(param.argument); if (param.argument.type === 'Identifier') {
param.argument = get_new_name(param.argument);
} else {
rename_identifiers(param.argument);
}
break; break;
case 'AssignmentPattern': case 'AssignmentPattern':

@ -224,7 +224,7 @@ export default {
}, },
invalid_rest_eachblock_binding: (rest_element_name: string) => ({ invalid_rest_eachblock_binding: (rest_element_name: string) => ({
code: 'invalid-rest-eachblock-binding', code: 'invalid-rest-eachblock-binding',
message: `...${rest_element_name} operator will create a new object and binding propagation with original object will not work` message: `The rest operator (...) will create a new object and binding '${rest_element_name}' with the original object will not work`
}), }),
avoid_mouse_events_on_document: { avoid_mouse_events_on_document: {
code: 'avoid-mouse-events-on-document', code: 'avoid-mouse-events-on-document',

@ -9,6 +9,7 @@ import { TemplateNode } from '../../interfaces';
import Element from './Element'; import Element from './Element';
import InlineComponent from './InlineComponent'; import InlineComponent from './InlineComponent';
import Window from './Window'; import Window from './Window';
import Document from './Document';
import { clone } from '../../utils/clone'; import { clone } from '../../utils/clone';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
@ -36,7 +37,7 @@ export default class Binding extends Node {
is_contextual: boolean; is_contextual: boolean;
is_readonly: boolean; is_readonly: boolean;
constructor(component: Component, parent: Element | InlineComponent | Window, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Element | InlineComponent | Window | Document, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') { if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {

@ -1,14 +1,24 @@
import Node from './shared/Node'; import Node from './shared/Node';
import Binding from './Binding';
import EventHandler from './EventHandler'; import EventHandler from './EventHandler';
import fuzzymatch from '../../utils/fuzzymatch';
import Action from './Action'; import Action from './Action';
import Component from '../Component'; import Component from '../Component';
import list from '../../utils/list';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { Element } from '../../interfaces'; import { Element } from '../../interfaces';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors';
const valid_bindings = [
'fullscreenElement',
'visibilityState'
];
export default class Document extends Node { export default class Document extends Node {
type: 'Document'; type: 'Document';
handlers: EventHandler[] = []; handlers: EventHandler[] = [];
bindings: Binding[] = [];
actions: Action[] = []; actions: Action[] = [];
constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) { constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) {
@ -17,6 +27,17 @@ export default class Document extends Node {
info.attributes.forEach((node) => { info.attributes.forEach((node) => {
if (node.type === 'EventHandler') { if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node)); this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Binding') {
if (!~valid_bindings.indexOf(node.name)) {
const match = fuzzymatch(node.name, valid_bindings);
if (match) {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:document>', ` (did you mean '${match}'?)`));
} else {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:document>', ` — valid bindings are ${list(valid_bindings)}`));
}
}
this.bindings.push(new Binding(component, this, scope, node));
} else if (node.type === 'Action') { } else if (node.type === 'Action') {
this.actions.push(new Action(component, this, scope, node)); this.actions.push(new Action(component, this, scope, node));
} else { } else {

@ -121,6 +121,7 @@ const a11y_implicit_semantics = new Map([
['details', 'group'], ['details', 'group'],
['dt', 'term'], ['dt', 'term'],
['fieldset', 'group'], ['fieldset', 'group'],
['figure', 'figure'],
['form', 'form'], ['form', 'form'],
['h1', 'heading'], ['h1', 'heading'],
['h2', 'heading'], ['h2', 'heading'],
@ -132,6 +133,7 @@ const a11y_implicit_semantics = new Map([
['img', 'img'], ['img', 'img'],
['li', 'listitem'], ['li', 'listitem'],
['link', 'link'], ['link', 'link'],
['main', 'main'],
['menu', 'list'], ['menu', 'list'],
['meter', 'progressbar'], ['meter', 'progressbar'],
['nav', 'navigation'], ['nav', 'navigation'],
@ -142,6 +144,7 @@ const a11y_implicit_semantics = new Map([
['progress', 'progressbar'], ['progress', 'progressbar'],
['section', 'region'], ['section', 'region'],
['summary', 'button'], ['summary', 'button'],
['table', 'table'],
['tbody', 'rowgroup'], ['tbody', 'rowgroup'],
['textarea', 'textbox'], ['textarea', 'textbox'],
['tfoot', 'rowgroup'], ['tfoot', 'rowgroup'],
@ -631,9 +634,7 @@ export default class Element extends Node {
} }
// no-redundant-roles // no-redundant-roles
const has_redundant_role = current_role === get_implicit_role(this.name, attribute_map); if (current_role === get_implicit_role(this.name, attribute_map)) {
if (this.name === current_role || has_redundant_role) {
component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role)); component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role));
} }

@ -17,6 +17,7 @@ const valid_bindings = [
'outerHeight', 'outerHeight',
'scrollX', 'scrollX',
'scrollY', 'scrollY',
'devicePixelRatio',
'online' 'online'
]; ];

@ -1,5 +1,5 @@
import { x } from 'code-red'; import { x } from 'code-red';
import { Node, Identifier, Expression, PrivateIdentifier } from 'estree'; import { Node, Identifier, Expression, PrivateIdentifier, Pattern } from 'estree';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import is_reference, { NodeWithPropertyDefinition } from 'is-reference'; import is_reference, { NodeWithPropertyDefinition } from 'is-reference';
import { clone } from '../../../utils/clone'; import { clone } from '../../../utils/clone';
@ -30,15 +30,17 @@ export function unpack_destructuring({
default_modifier = (node) => node, default_modifier = (node) => node,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element = false
}: { }: {
contexts: Context[]; contexts: Context[];
node: Node; node: Pattern;
modifier?: DestructuredVariable['modifier']; modifier?: DestructuredVariable['modifier'];
default_modifier?: DestructuredVariable['default_modifier']; default_modifier?: DestructuredVariable['default_modifier'];
scope: TemplateScope; scope: TemplateScope;
component: Component; component: Component;
context_rest_properties: Map<string, Node>; context_rest_properties: Map<string, Node>;
in_rest_element?: boolean;
}) { }) {
if (!node) return; if (!node) return;
@ -49,28 +51,26 @@ export function unpack_destructuring({
modifier, modifier,
default_modifier default_modifier
}); });
} else if (node.type === 'RestElement') {
contexts.push({ if (in_rest_element) {
type: 'DestructuredVariable', context_rest_properties.set(node.name, node);
key: node.argument as Identifier, }
modifier,
default_modifier
});
context_rest_properties.set((node.argument as Identifier).name, node);
} else if (node.type === 'ArrayPattern') { } else if (node.type === 'ArrayPattern') {
node.elements.forEach((element, i) => { node.elements.forEach((element: Pattern | null, i: number) => {
if (element && element.type === 'RestElement') { if (!element) {
return;
} else if (element.type === 'RestElement') {
unpack_destructuring({ unpack_destructuring({
contexts, contexts,
node: element, node: element.argument,
modifier: (node) => x`${modifier(node)}.slice(${i})` as Node, modifier: (node) => x`${modifier(node)}.slice(${i})` as Node,
default_modifier, default_modifier,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element: true
}); });
context_rest_properties.set((element.argument as Identifier).name, element); } else if (element.type === 'AssignmentPattern') {
} else if (element && element.type === 'AssignmentPattern') {
const n = contexts.length; const n = contexts.length;
mark_referenced(element.right, scope, component); mark_referenced(element.right, scope, component);
@ -87,7 +87,8 @@ export function unpack_destructuring({
)}` as Node, )}` as Node,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element
}); });
} else { } else {
unpack_destructuring({ unpack_destructuring({
@ -97,7 +98,8 @@ export function unpack_destructuring({
default_modifier, default_modifier,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element
}); });
} }
}); });
@ -116,9 +118,9 @@ export function unpack_destructuring({
default_modifier, default_modifier,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element: true
}); });
context_rest_properties.set((property.argument as Identifier).name, property);
} else if (property.type === 'Property') { } else if (property.type === 'Property') {
const key = property.key; const key = property.key;
const value = property.value; const value = property.value;
@ -168,7 +170,8 @@ export function unpack_destructuring({
)}` as Node, )}` as Node,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element
}); });
} else { } else {
// e.g. { property } or { property: newName } // e.g. { property } or { property: newName }
@ -179,7 +182,8 @@ export function unpack_destructuring({
default_modifier, default_modifier,
scope, scope,
component, component,
context_rest_properties context_rest_properties,
in_rest_element
}); });
} }
} }

@ -1,6 +1,6 @@
import Block from '../Block'; import Block from '../Block';
import Wrapper from './shared/Wrapper'; import Wrapper from './shared/Wrapper';
import { x } from 'code-red'; import { b, x } from 'code-red';
import Document from '../../nodes/Document'; import Document from '../../nodes/Document';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import EventHandler from './Element/EventHandler'; import EventHandler from './Element/EventHandler';
@ -9,6 +9,16 @@ import { TemplateNode } from '../../../interfaces';
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import add_actions from './shared/add_actions'; import add_actions from './shared/add_actions';
const associated_events = {
fullscreenElement: ['fullscreenchange'],
visibilityState: ['visibilitychange']
};
const readonly = new Set([
'fullscreenElement',
'visibilityState'
]);
export default class DocumentWrapper extends Wrapper { export default class DocumentWrapper extends Wrapper {
node: Document; node: Document;
handlers: EventHandler[]; handlers: EventHandler[];
@ -19,7 +29,66 @@ export default class DocumentWrapper extends Wrapper {
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
const { renderer } = this;
const { component } = renderer;
const events: Record<string, Array<{ name: string; value: string }>> = {};
const bindings: Record<string, string> = {};
add_event_handlers(block, x`@_document`, this.handlers); add_event_handlers(block, x`@_document`, this.handlers);
add_actions(block, x`@_document`, this.node.actions); add_actions(block, x`@_document`, this.node.actions);
this.node.bindings.forEach(binding => {
// TODO: what if it's a MemberExpression?
const binding_name = (binding.expression.node as Identifier).name;
// in dev mode, throw if read-only values are written to
if (readonly.has(binding.name)) {
renderer.readonly.add(binding_name);
}
bindings[binding.name] = binding_name;
const binding_events = associated_events[binding.name];
const property = binding.name;
binding_events.forEach(associated_event => {
if (!events[associated_event]) events[associated_event] = [];
events[associated_event].push({
name: binding_name,
value: property
});
});
});
Object.keys(events).forEach(event => {
const id = block.get_unique_name(`ondocument${event}`);
const props = events[event];
renderer.add_to_context(id.name);
const fn = renderer.reference(id.name);
props.forEach(prop => {
renderer.meta_bindings.push(
b`this._state.${prop.name} = @_document.${prop.value};`
);
});
block.event_listeners.push(x`
@listen(@_document, "${event}", ${fn})
`);
component.partly_hoisted.push(b`
function ${id}() {
${props.map(prop => renderer.invalidate(prop.name, x`${prop.name} = @_document.${prop.value}`))}
}
`);
block.chunks.init.push(b`
@add_render_callback(${fn});
`);
component.has_reactive_assignments = true;
});
} }
} }

@ -14,6 +14,7 @@ const associated_events = {
innerHeight: 'resize', innerHeight: 'resize',
outerWidth: 'resize', outerWidth: 'resize',
outerHeight: 'resize', outerHeight: 'resize',
devicePixelRatio: 'resize',
scrollX: 'scroll', scrollX: 'scroll',
scrollY: 'scroll' scrollY: 'scroll'
@ -29,6 +30,7 @@ const readonly = new Set([
'innerHeight', 'innerHeight',
'outerWidth', 'outerWidth',
'outerHeight', 'outerHeight',
'devicePixelRatio',
'online' 'online'
]); ]);

@ -0,0 +1,12 @@
export default {
html: `
<h1>1</h1>
<h1>2</h1>
<h1>3</h1>
<h1>5</h1>
<h1>10</h1>
<h1>20</h1>
<h1>30</h1>
<h1>6</h1>
`
};

@ -0,0 +1,15 @@
<script>
let [first, second, ...[third, ...[, fifth]]] = [1, 2, 3, 4, 5];
let [one, two, ...[three, ...{ length }]] = [10, 20, 30, 40, 50, 60, 70, 80, 90];
</script>
<h1>{first}</h1>
<h1>{second}</h1>
<h1>{third}</h1>
<h1>{fifth}</h1>
<h1>{one}</h1>
<h1>{two}</h1>
<h1>{three}</h1>
<h1>{length}</h1>

@ -0,0 +1,69 @@
export default {
props: {
thePromise: new Promise(_ => {})
},
html: `
loading...
`,
async test({ assert, component, target }) {
await (component.thePromise = Promise.resolve([1, 2, 3, 4, 5, 6, 7, 8]));
assert.htmlEqual(
target.innerHTML,
`
<p>a: 1</p>
<p>b: 2</p>
<p>c: 5</p>
<p>remaining length: 3</p>
`
);
await (component.thePromise = Promise.resolve([9, 10, 11, 12, 13, 14, 15]));
assert.htmlEqual(
target.innerHTML,
`
<p>a: 9</p>
<p>b: 10</p>
<p>c: 13</p>
<p>remaining length: 2</p>
`
);
try {
await (component.thePromise = Promise.reject([16, 17, 18, 19, 20, 21, 22]));
} catch (e) {
// do nothing
}
assert.htmlEqual(
target.innerHTML,
`
<p>c: 16</p>
<p>d: 17</p>
<p>e: 18</p>
<p>f: 19</p>
<p>g: 22</p>
`
);
try {
await (component.thePromise = Promise.reject([23, 24, 25, 26, 27, 28, 29, 30, 31]));
} catch (e) {
// do nothing
}
assert.htmlEqual(
target.innerHTML,
`
<p>c: 23</p>
<p>d: 24</p>
<p>e: 25</p>
<p>f: 26</p>
<p>g: 29</p>
`
);
}
};

@ -0,0 +1,18 @@
<script>
export let thePromise;
</script>
{#await thePromise}
loading...
{:then [ a, b, ...[,, c, ...{ length } ]]}
<p>a: {a}</p>
<p>b: {b}</p>
<p>c: {c}</p>
<p>remaining length: {length}</p>
{:catch [c, ...[d, e, f, ...[,,g]]]}
<p>c: {c}</p>
<p>d: {d}</p>
<p>e: {e}</p>
<p>f: {f}</p>
<p>g: {g}</p>
{/await}

@ -0,0 +1,19 @@
export default {
html: '<div>12 120 70, 30+4=34</div>',
async test({ component, target, assert }) {
component.promise1 = Promise.resolve({width: 5, height: 6});
component.promise2 = Promise.reject({width: 6, height: 7});
await Promise.resolve();
assert.htmlEqual(target.innerHTML, `
<div>30 300 110, 50+6=56</div>
<div>42 420 130, 60+7=67</div>
`);
component.constant = 20;
assert.htmlEqual(target.innerHTML, `
<div>30 600 220, 100+6=106</div>
<div>42 840 260, 120+7=127</div>
`);
}
};

@ -0,0 +1,23 @@
<script>
export let promise1 = {width: 3, height: 4};
export let promise2 = {width: 5, height: 7};
export let constant = 10;
function calculate(width, height, constant) {
return { area: width * height, volume: width * height * constant };
}
</script>
{#await promise1 then { width, height }}
{@const {area, volume} = calculate(width, height, constant)}
{@const perimeter = (width + height) * constant}
{@const [_width, ...[_height, ...[sum]]] = [width * constant, height, width * constant + height]}
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
{/await}
{#await promise2 catch { width, height }}
{@const {area, volume} = calculate(width, height, constant)}
{@const perimeter = (width + height) * constant}
{@const [_width, ...[_height, ...[sum]]] = [width * constant, height, width * constant + height]}
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
{/await}

@ -0,0 +1,30 @@
export default {
html: `
<div>12 120 70, 30+4=34</div>
<div>35 350 120, 50+7=57</div>
<div>48 480 140, 60+8=68</div>
`,
async test({ component, target, assert }) {
component.constant = 20;
assert.htmlEqual(target.innerHTML, `
<div>12 240 140, 60+4=64</div>
<div>35 700 240, 100+7=107</div>
<div>48 960 280, 120+8=128</div>
`);
component.boxes = [
{width: 3, height: 4},
{width: 4, height: 5},
{width: 5, height: 6},
{width: 6, height: 7}
];
assert.htmlEqual(target.innerHTML, `
<div>12 240 140, 60+4=64</div>
<div>20 400 180, 80+5=85</div>
<div>30 600 220, 100+6=106</div>
<div>42 840 260, 120+7=127</div>
`);
}
};

@ -0,0 +1,19 @@
<script>
export let boxes = [
{width: 3, height: 4},
{width: 5, height: 7},
{width: 6, height: 8},
];
export let constant = 10;
function calculate(width, height, constant) {
return { area: width * height, volume: width * height * constant };
}
</script>
{#each boxes as { width, height }}
{@const {area, volume} = calculate(width, height, constant)}
{@const perimeter = (width + height) * constant}
{@const [_width, ...[_height, ...[sum]]] = [width * constant, height, width * constant + height]}
<div>{area} {volume} {perimeter}, {_width}+{_height}={sum}</div>
{/each}

@ -0,0 +1,25 @@
<script>
import { writable } from 'svelte/store';
const THING = { a: 1, b: { c: 2, d: [3, 4, writable(5), 6, 7] }, e: [6], h: 8 };
const default_g = 9;
export let { a, b: { c, d: [d_one, ...[, ...[d_three, ...{ length }]]], f }, e: [e_one], g = default_g } = THING;
export const { a: A, b: { c: C } } = THING;
</script>
<div>
a: {a},
b: {typeof b},
c: {c},
d_one: {d_one},
d_three: {$d_three},
length: {length},
f: {f},
g: {g},
e: {typeof e},
e_one: {e_one},
A: {A},
C: {C}
</div>
<div>{JSON.stringify(THING)}</div>

@ -0,0 +1,9 @@
export default {
html: `
<div>a: 1, b: undefined, c: 2, d_one: 3, d_three: 5, length: 2, f: undefined, g: 9, e: undefined, e_one: 6, A: 1, C: 2</div>
<div>{"a":1,"b":{"c":2,"d":[3,4,{},6,7]},"e":[6],"h":8}</div>
<br>
<div>a: a, b: undefined, c: 2, d_one: d_one, d_three: 5, length: 7, f: f, g: g, e: undefined, e_one: 6, A: 1, C: 2</div>
<div>{"a":1,"b":{"c":2,"d":[3,4,{},6,7]},"e":[6],"h":8}</div>
`
};

@ -0,0 +1,7 @@
<script>
import A from './A.svelte';
</script>
<A />
<br />
<A a="a" d_one="d_one" list_one="list_one" f="f" list_two_b="list_two_b" g="g" A="A" C="C" length={7} />

@ -0,0 +1,23 @@
<script>
import { writable } from "svelte/store";
let default_b = 5;
const LIST = [1, 2, 3, { a: 4 }, [5, writable(6), writable(7), 8]];
export const [
x,
,
...[, { a: list_two_a, b: list_two_b = default_b }, [, ...{ length: y }]]
] = LIST;
export let [
l,
m,
,
...[{ a: n, b: o = default_b }, [p, q, ...[r, ...{ length: s }]]]
] = LIST;
</script>
<div>
x: {x}, list_two_a: {list_two_a}, list_two_b: {list_two_b}, y: {y}, l: {l}, m: {m},
n: {n}, o: {o}, p: {p}, q: {$q}, r: {$r}, s: {s}
</div>
<div>{JSON.stringify(LIST)}</div>

@ -0,0 +1,19 @@
export default {
html: `
<div>x: 1, list_two_a: 4, list_two_b: 5, y: 3, l: 1, m: 2, n: 4, o: 5, p: 5, q: 6, r: 7, s: 1</div>
<div>[1,2,3,{"a":4},[5,{},{},8]]</div>
<br><div>x: 1, list_two_a: 4, list_two_b: 5, y: 3, l: l, m: m, n: n, o: o, p: p, q: q, r: r, s: s</div>
<div>[1,2,3,{"a":4},[5,{},{},8]]</div>
`,
async test({ component, assert, target }) {
await component.update();
assert.htmlEqual(target.innerHTML, `
<div>x: 1, list_two_a: 4, list_two_b: 5, y: 3, l: 1, m: 2, n: 4, o: 5, p: 5, q: 6, r: 7, s: 1</div>
<div>[1,2,3,{"a":4},[5,{},{},8]]</div>
<br><div>x: 1, list_two_a: 4, list_two_b: 5, y: 3, l: LL, m: MM, n: NN, o: OO, p: PP, q: QQ, r: RR, s: SS</div>
<div>[1,2,3,{"a":4},[5,{},{},8]]</div>
`);
}
};

@ -0,0 +1,36 @@
<script>
import A from "./A.svelte";
import { writable } from "svelte/store";
let x = "x",
list_two_a = "list_two_a",
list_two_b = "list_two_b",
y = writable("y"),
l = "l",
m = "m",
n = "n",
o = "o",
p = "p",
q = writable("q"),
r = writable("r"),
s = "s";
export function update() {
x = "XX";
list_two_a = "LIST_TWO_A";
list_two_b = "LIST_TWO_B";
y = writable("YY");
l = "LL";
m = "MM";
n = "NN";
o = "OO";
p = "PP";
q = writable("QQ");
r = writable("RR");
s = "SS";
}
</script>
<A />
<br />
<A {x} {list_two_a} {list_two_b} {y} {l} {m} {n} {o} {p} {q} {r} {s} />

@ -0,0 +1,31 @@
export default {
before_test() {
Object.defineProperties(window.document, {
fullscreenElement: {
value: null,
configurable: true
}
});
},
// copied from window-binding
// there's some kind of weird bug with this test... it compiles with the wrong require.extensions hook for some bizarre reason
skip_if_ssr: true,
async test({ assert, target, window, component }) {
const event = new window.Event('fullscreenchange');
const div = target.querySelector('div');
Object.defineProperties(window.document, {
fullscreenElement: {
value: div,
configurable: true
}
});
window.document.dispatchEvent(event);
assert.equal(component.fullscreen, div);
}
};

@ -0,0 +1,7 @@
<script>
export let fullscreen;
</script>
<svelte:document bind:fullscreenElement={fullscreen}/>
<div />

@ -0,0 +1,24 @@
export default {
props: {
array: [
[1, 2, 3, 4, 5],
[6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16, 17, 18, 19, 20, 21, 22]
]
},
html: `
<p>First: 1, Second: 2, Third: 3, Elements remaining: 2</p>
<p>First: 6, Second: 7, Third: 8, Elements remaining: 0</p>
<p>First: 9, Second: 10, Third: 11, Elements remaining: 1</p>
<p>First: 13, Second: 14, Third: 15, Elements remaining: 7</p>
`,
test({ assert, component, target }) {
component.array = [[23, 24, 25, 26, 27, 28, 29]];
assert.htmlEqual( target.innerHTML, `
<p>First: 23, Second: 24, Third: 25, Elements remaining: 4</p>
`);
}
};

@ -0,0 +1,9 @@
<script>
export let array;
</script>
{#each array as [first, second, ...[third, ...{ length }]]}
<p>
First: {first}, Second: {second}, Third: {third}, Elements remaining: {length}
</p>
{/each}

@ -1,5 +1,5 @@
export default { export default {
html: '<div>1024x768</div>', html: '<div>1024x768</div><div>1</div>',
before_test() { before_test() {
Object.defineProperties(window, { Object.defineProperties(window, {
@ -10,6 +10,10 @@ export default {
innerHeight: { innerHeight: {
value: 768, value: 768,
configurable: true configurable: true
},
devicePixelRatio: {
value: 1,
configurable: true
} }
}); });
}, },
@ -27,13 +31,17 @@ export default {
innerHeight: { innerHeight: {
value: 456, value: 456,
configurable: true configurable: true
},
devicePixelRatio: {
value: 2,
configurable: true
} }
}); });
await window.dispatchEvent(event); await window.dispatchEvent(event);
assert.htmlEqual(target.innerHTML, ` assert.htmlEqual(target.innerHTML, `
<div>567x456</div> <div>567x456</div><div>2</div>
`); `);
} }
}; };

@ -1,8 +1,10 @@
<script> <script>
export let width; export let width;
export let height; export let height;
export let devicePixelRatio;
</script> </script>
<svelte:window bind:innerWidth={width} bind:innerHeight={height}/> <svelte:window bind:innerWidth={width} bind:innerHeight={height} bind:devicePixelRatio={devicePixelRatio}/>
<div>{width}x{height}</div> <div>{width}x{height}</div>
<div>{devicePixelRatio}</div>

@ -41,4 +41,8 @@
<!-- Tested header/footer not nested in section/article --> <!-- Tested header/footer not nested in section/article -->
<header role="banner"></header> <header role="banner"></header>
<footer role="contentinfo"></footer> <footer role="contentinfo"></footer>
<!-- Allowed -->
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
<menu role="menu" />

@ -1,8 +1,8 @@
[ [
{ {
"code": "invalid-rest-eachblock-binding", "code": "invalid-rest-eachblock-binding",
"message": "...rest operator will create a new object and binding propagation with original object will not work", "message": "The rest operator (...) will create a new object and binding 'rest' with the original object will not work",
"start": { "line": 8, "column": 24 }, "start": { "line": 8, "column": 27 },
"end": { "line": 8, "column": 31 } "end": { "line": 8, "column": 31 }
} }
] ]

@ -1,8 +1,8 @@
[ [
{ {
"code": "invalid-rest-eachblock-binding", "code": "invalid-rest-eachblock-binding",
"message": "...rest operator will create a new object and binding propagation with original object will not work", "message": "The rest operator (...) will create a new object and binding 'rest' with the original object will not work",
"start": { "line": 5, "column": 32 }, "start": { "line": 5, "column": 35 },
"end": { "line": 5, "column": 39 } "end": { "line": 5, "column": 39 }
} }
] ]

@ -0,0 +1,9 @@
<script>
const a = [[1, 2, 3, 4, 5]];
</script>
{#each a as [first, second, ...[third, ...{ length }]]}
<p>{first}, {second}, {length}</p>
<input bind:value={third} />
<input bind:value={length} />
{/each}

@ -0,0 +1,26 @@
[
{
"code": "invalid-rest-eachblock-binding",
"end": {
"column": 37,
"line": 5
},
"message": "The rest operator (...) will create a new object and binding 'third' with the original object will not work",
"start": {
"column": 32,
"line": 5
}
},
{
"code": "invalid-rest-eachblock-binding",
"end": {
"column": 50,
"line": 5
},
"message": "The rest operator (...) will create a new object and binding 'length' with the original object will not work",
"start": {
"column": 44,
"line": 5
}
}
]

@ -1,8 +1,8 @@
[ [
{ {
"code": "invalid-rest-eachblock-binding", "code": "invalid-rest-eachblock-binding",
"message": "...rest operator will create a new object and binding propagation with original object will not work", "message": "The rest operator (...) will create a new object and binding 'rest' with the original object will not work",
"start": { "line": 5, "column": 25 }, "start": { "line": 5, "column": 28 },
"end": { "line": 5, "column": 32 } "end": { "line": 5, "column": 32 }
} }
] ]

@ -1,6 +1,6 @@
[{ [{
"code": "invalid-binding", "code": "invalid-binding",
"message": "'potato' is not a valid binding on <svelte:window> — valid bindings are innerWidth, innerHeight, outerWidth, outerHeight, scrollX, scrollY or online", "message": "'potato' is not a valid binding on <svelte:window> — valid bindings are innerWidth, innerHeight, outerWidth, outerHeight, scrollX, scrollY, devicePixelRatio or online",
"start": { "start": {
"line": 1, "line": 1,
"column": 15 "column": 15

Loading…
Cancel
Save