diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 05b48f2ae0..28234b69df 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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 - [ ] 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:`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01f8728cc0..943006267e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,6 +62,8 @@ When [opening a new issue](https://github.com/sveltejs/svelte/issues/new/choose) ## 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 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). diff --git a/elements/index.d.ts b/elements/index.d.ts index ac32ae94c3..e7ed8901af 100644 --- a/elements/index.d.ts +++ b/elements/index.d.ts @@ -1078,11 +1078,17 @@ export interface SvelteMediaTimeRange { end: number; } +export interface SvelteDocumentAttributes extends HTMLAttributes { + readonly 'bind:fullscreenElement'?: Document['fullscreenElement'] | undefined | null; + readonly 'bind:visibilityState'?: Document['visibilityState'] | undefined | null; +} + export interface SvelteWindowAttributes extends HTMLAttributes { readonly 'bind:innerWidth'?: Window['innerWidth'] | undefined | null; readonly 'bind:innerHeight'?: Window['innerHeight'] | undefined | null; readonly 'bind:outerWidth'?: Window['outerWidth'] | undefined | null; readonly 'bind:outerHeight'?: Window['outerHeight'] | undefined | null; + readonly 'bind:devicePixelRatio'?: Window['devicePixelRatio'] | undefined | null; 'bind:scrollX'?: Window['scrollX'] | undefined | null; 'bind:scrollY'?: Window['scrollY'] | undefined | null; readonly 'bind:online'?: Window['navigator']['onLine'] | undefined | null; @@ -1591,7 +1597,7 @@ export interface SvelteHTMLElements { // Svelte specific 'svelte:window': SvelteWindowAttributes; - 'svelte:document': HTMLAttributes; + 'svelte:document': SvelteDocumentAttributes; 'svelte:body': HTMLAttributes; 'svelte:fragment': { slot?: string }; 'svelte:options': { [name: string]: any }; diff --git a/site/content/docs/03-template-syntax.md b/site/content/docs/03-template-syntax.md index 170c303b85..8038e972be 100644 --- a/site/content/docs/03-template-syntax.md +++ b/site/content/docs/03-template-syntax.md @@ -823,6 +823,8 @@ Inputs that work together can use `bind:group`. ``` +> `bind:group` only works if the inputs are in the same Svelte component. + #### bind:this ```sv @@ -1742,6 +1744,7 @@ You can also bind to the following properties: * `scrollX` * `scrollY` * `online` — an alias for `window.navigator.onLine` +* `devicePixelRatio` All except `scrollX` and `scrollY` are readonly. @@ -1756,6 +1759,9 @@ All except `scrollX` and `scrollY` are readonly. ```sv ``` +```sv + +``` --- @@ -1770,6 +1776,15 @@ As with ``, this element may only appear the top level of your co /> ``` +--- + +You can also bind to the following properties: + +* `fullscreenElement` +* `visibilityState` + +All are readonly. + ### `` ```sv diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index e87cf6218a..a756f8e3c4 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -25,7 +25,7 @@ import TemplateScope from './nodes/shared/TemplateScope'; import fuzzymatch from '../utils/fuzzymatch'; import get_object from './utils/get_object'; 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 check_graph_for_cycles from './utils/check_graph_for_cycles'; import { print, b } from 'code-red'; @@ -1034,7 +1034,7 @@ export default class Component { const inserts = []; const props = []; - function add_new_props(exported, local, default_value) { + function add_new_props(exported: Identifier, local: Pattern, default_value: Expression) { props.push({ type: 'Property', method: false, @@ -1064,7 +1064,7 @@ export default class Component { for (let index = 0; index < node.declarations.length; index++) { const declarator = node.declarations[index]; 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); if (variable.subscribable) { inserts.push(get_insert(variable)); @@ -1078,7 +1078,7 @@ export default class Component { return local; } - function rename_identifiers(param: Node) { + function rename_identifiers(param: Pattern) { switch (param.type) { case 'ObjectPattern': { const handle_prop = (prop: Property | RestElement) => { @@ -1087,7 +1087,7 @@ export default class Component { } else if (prop.value.type === 'Identifier') { prop.value = get_new_name(prop.value); } else { - rename_identifiers(prop.value); + rename_identifiers(prop.value as Pattern); } }; @@ -1095,7 +1095,7 @@ export default class Component { break; } case 'ArrayPattern': { - const handle_element = (element: Node, index: number, array: Node[]) => { + const handle_element = (element: Pattern | null, index: number, array: Array) => { if (element) { if (element.type === 'Identifier') { array[index] = get_new_name(element); @@ -1110,7 +1110,11 @@ export default class Component { } 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; case 'AssignmentPattern': diff --git a/src/compiler/compile/compiler_warnings.ts b/src/compiler/compile/compiler_warnings.ts index 2f8ade43e2..c0d595f20d 100644 --- a/src/compiler/compile/compiler_warnings.ts +++ b/src/compiler/compile/compiler_warnings.ts @@ -224,7 +224,7 @@ export default { }, invalid_rest_eachblock_binding: (rest_element_name: string) => ({ 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: { code: 'avoid-mouse-events-on-document', diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 303506222f..f655554c81 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -9,6 +9,7 @@ import { TemplateNode } from '../../interfaces'; import Element from './Element'; import InlineComponent from './InlineComponent'; import Window from './Window'; +import Document from './Document'; import { clone } from '../../utils/clone'; import compiler_errors from '../compiler_errors'; import compiler_warnings from '../compiler_warnings'; @@ -36,7 +37,7 @@ export default class Binding extends Node { is_contextual: 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); if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') { diff --git a/src/compiler/compile/nodes/Document.ts b/src/compiler/compile/nodes/Document.ts index 653ccb627b..60264aa40e 100644 --- a/src/compiler/compile/nodes/Document.ts +++ b/src/compiler/compile/nodes/Document.ts @@ -1,14 +1,24 @@ import Node from './shared/Node'; +import Binding from './Binding'; import EventHandler from './EventHandler'; +import fuzzymatch from '../../utils/fuzzymatch'; import Action from './Action'; import Component from '../Component'; +import list from '../../utils/list'; import TemplateScope from './shared/TemplateScope'; import { Element } from '../../interfaces'; import compiler_warnings from '../compiler_warnings'; +import compiler_errors from '../compiler_errors'; + +const valid_bindings = [ + 'fullscreenElement', + 'visibilityState' +]; export default class Document extends Node { type: 'Document'; handlers: EventHandler[] = []; + bindings: Binding[] = []; actions: Action[] = []; constructor(component: Component, parent: Node, scope: TemplateScope, info: Element) { @@ -17,6 +27,17 @@ export default class Document extends Node { info.attributes.forEach((node) => { if (node.type === 'EventHandler') { 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, '', ` (did you mean '${match}'?)`)); + } else { + return component.error(node, compiler_errors.invalid_binding_on(node.name, '', ` — valid bindings are ${list(valid_bindings)}`)); + } + } + + this.bindings.push(new Binding(component, this, scope, node)); } else if (node.type === 'Action') { this.actions.push(new Action(component, this, scope, node)); } else { diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 81e61ef86b..9d45c84fae 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -121,6 +121,7 @@ const a11y_implicit_semantics = new Map([ ['details', 'group'], ['dt', 'term'], ['fieldset', 'group'], + ['figure', 'figure'], ['form', 'form'], ['h1', 'heading'], ['h2', 'heading'], @@ -132,6 +133,7 @@ const a11y_implicit_semantics = new Map([ ['img', 'img'], ['li', 'listitem'], ['link', 'link'], + ['main', 'main'], ['menu', 'list'], ['meter', 'progressbar'], ['nav', 'navigation'], @@ -142,6 +144,7 @@ const a11y_implicit_semantics = new Map([ ['progress', 'progressbar'], ['section', 'region'], ['summary', 'button'], + ['table', 'table'], ['tbody', 'rowgroup'], ['textarea', 'textbox'], ['tfoot', 'rowgroup'], @@ -631,9 +634,7 @@ export default class Element extends Node { } // no-redundant-roles - const has_redundant_role = current_role === get_implicit_role(this.name, attribute_map); - - if (this.name === current_role || has_redundant_role) { + if (current_role === get_implicit_role(this.name, attribute_map)) { component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role)); } diff --git a/src/compiler/compile/nodes/Window.ts b/src/compiler/compile/nodes/Window.ts index c5bec0acd3..2f8a015d8a 100644 --- a/src/compiler/compile/nodes/Window.ts +++ b/src/compiler/compile/nodes/Window.ts @@ -17,6 +17,7 @@ const valid_bindings = [ 'outerHeight', 'scrollX', 'scrollY', + 'devicePixelRatio', 'online' ]; diff --git a/src/compiler/compile/nodes/shared/Context.ts b/src/compiler/compile/nodes/shared/Context.ts index 76ac895681..43d4d1f626 100644 --- a/src/compiler/compile/nodes/shared/Context.ts +++ b/src/compiler/compile/nodes/shared/Context.ts @@ -1,5 +1,5 @@ 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 is_reference, { NodeWithPropertyDefinition } from 'is-reference'; import { clone } from '../../../utils/clone'; @@ -30,15 +30,17 @@ export function unpack_destructuring({ default_modifier = (node) => node, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element = false }: { contexts: Context[]; - node: Node; + node: Pattern; modifier?: DestructuredVariable['modifier']; default_modifier?: DestructuredVariable['default_modifier']; scope: TemplateScope; component: Component; context_rest_properties: Map; + in_rest_element?: boolean; }) { if (!node) return; @@ -49,28 +51,26 @@ export function unpack_destructuring({ modifier, default_modifier }); - } else if (node.type === 'RestElement') { - contexts.push({ - type: 'DestructuredVariable', - key: node.argument as Identifier, - modifier, - default_modifier - }); - context_rest_properties.set((node.argument as Identifier).name, node); + + if (in_rest_element) { + context_rest_properties.set(node.name, node); + } } else if (node.type === 'ArrayPattern') { - node.elements.forEach((element, i) => { - if (element && element.type === 'RestElement') { + node.elements.forEach((element: Pattern | null, i: number) => { + if (!element) { + return; + } else if (element.type === 'RestElement') { unpack_destructuring({ contexts, - node: element, + node: element.argument, modifier: (node) => x`${modifier(node)}.slice(${i})` as Node, default_modifier, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element: true }); - context_rest_properties.set((element.argument as Identifier).name, element); - } else if (element && element.type === 'AssignmentPattern') { + } else if (element.type === 'AssignmentPattern') { const n = contexts.length; mark_referenced(element.right, scope, component); @@ -87,7 +87,8 @@ export function unpack_destructuring({ )}` as Node, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element }); } else { unpack_destructuring({ @@ -97,7 +98,8 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element }); } }); @@ -116,9 +118,9 @@ export function unpack_destructuring({ default_modifier, scope, 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') { const key = property.key; const value = property.value; @@ -168,7 +170,8 @@ export function unpack_destructuring({ )}` as Node, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element }); } else { // e.g. { property } or { property: newName } @@ -179,7 +182,8 @@ export function unpack_destructuring({ default_modifier, scope, component, - context_rest_properties + context_rest_properties, + in_rest_element }); } } diff --git a/src/compiler/compile/render_dom/wrappers/Document.ts b/src/compiler/compile/render_dom/wrappers/Document.ts index 4f7c86c54f..0a9565e64c 100644 --- a/src/compiler/compile/render_dom/wrappers/Document.ts +++ b/src/compiler/compile/render_dom/wrappers/Document.ts @@ -1,6 +1,6 @@ import Block from '../Block'; import Wrapper from './shared/Wrapper'; -import { x } from 'code-red'; +import { b, x } from 'code-red'; import Document from '../../nodes/Document'; import { Identifier } from 'estree'; import EventHandler from './Element/EventHandler'; @@ -9,6 +9,16 @@ import { TemplateNode } from '../../../interfaces'; import Renderer from '../Renderer'; 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 { node: Document; handlers: EventHandler[]; @@ -19,7 +29,66 @@ export default class DocumentWrapper extends Wrapper { } render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { + const { renderer } = this; + const { component } = renderer; + + const events: Record> = {}; + const bindings: Record = {}; + add_event_handlers(block, x`@_document`, this.handlers); 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; + }); } } diff --git a/src/compiler/compile/render_dom/wrappers/Window.ts b/src/compiler/compile/render_dom/wrappers/Window.ts index c98af18268..9e58bbcaad 100644 --- a/src/compiler/compile/render_dom/wrappers/Window.ts +++ b/src/compiler/compile/render_dom/wrappers/Window.ts @@ -14,6 +14,7 @@ const associated_events = { innerHeight: 'resize', outerWidth: 'resize', outerHeight: 'resize', + devicePixelRatio: 'resize', scrollX: 'scroll', scrollY: 'scroll' @@ -29,6 +30,7 @@ const readonly = new Set([ 'innerHeight', 'outerWidth', 'outerHeight', + 'devicePixelRatio', 'online' ]); diff --git a/test/runtime/samples/array-rest-is-array-or-object/_config.js b/test/runtime/samples/array-rest-is-array-or-object/_config.js new file mode 100644 index 0000000000..c971e109ce --- /dev/null +++ b/test/runtime/samples/array-rest-is-array-or-object/_config.js @@ -0,0 +1,12 @@ +export default { + html: ` +

1

+

2

+

3

+

5

+

10

+

20

+

30

+

6

+ ` +}; diff --git a/test/runtime/samples/array-rest-is-array-or-object/main.svelte b/test/runtime/samples/array-rest-is-array-or-object/main.svelte new file mode 100644 index 0000000000..fb3a5b7d85 --- /dev/null +++ b/test/runtime/samples/array-rest-is-array-or-object/main.svelte @@ -0,0 +1,15 @@ + + +

{first}

+

{second}

+

{third}

+

{fifth}

+ +

{one}

+

{two}

+

{three}

+

{length}

+ diff --git a/test/runtime/samples/await-then-destruct-array-nested-rest/_config.js b/test/runtime/samples/await-then-destruct-array-nested-rest/_config.js new file mode 100644 index 0000000000..9a287e35e2 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-array-nested-rest/_config.js @@ -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, + ` +

a: 1

+

b: 2

+

c: 5

+

remaining length: 3

+ ` + ); + + await (component.thePromise = Promise.resolve([9, 10, 11, 12, 13, 14, 15])); + + assert.htmlEqual( + target.innerHTML, + ` +

a: 9

+

b: 10

+

c: 13

+

remaining length: 2

+ ` + ); + + try { + await (component.thePromise = Promise.reject([16, 17, 18, 19, 20, 21, 22])); + } catch (e) { + // do nothing + } + + assert.htmlEqual( + target.innerHTML, + ` +

c: 16

+

d: 17

+

e: 18

+

f: 19

+

g: 22

+ ` + ); + + try { + await (component.thePromise = Promise.reject([23, 24, 25, 26, 27, 28, 29, 30, 31])); + } catch (e) { + // do nothing + } + + assert.htmlEqual( + target.innerHTML, + ` +

c: 23

+

d: 24

+

e: 25

+

f: 26

+

g: 29

+ ` + ); + } +}; diff --git a/test/runtime/samples/await-then-destruct-array-nested-rest/main.svelte b/test/runtime/samples/await-then-destruct-array-nested-rest/main.svelte new file mode 100644 index 0000000000..4bb8ad0077 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-array-nested-rest/main.svelte @@ -0,0 +1,18 @@ + + +{#await thePromise} + loading... +{:then [ a, b, ...[,, c, ...{ length } ]]} +

a: {a}

+

b: {b}

+

c: {c}

+

remaining length: {length}

+{:catch [c, ...[d, e, f, ...[,,g]]]} +

c: {c}

+

d: {d}

+

e: {e}

+

f: {f}

+

g: {g}

+{/await} diff --git a/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/_config.js b/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/_config.js new file mode 100644 index 0000000000..cb40e7c456 --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/_config.js @@ -0,0 +1,19 @@ +export default { + html: '
12 120 70, 30+4=34
', + 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, ` +
30 300 110, 50+6=56
+
42 420 130, 60+7=67
+ `); + + component.constant = 20; + assert.htmlEqual(target.innerHTML, ` +
30 600 220, 100+6=106
+
42 840 260, 120+7=127
+ `); + } +}; diff --git a/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/main.svelte b/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/main.svelte new file mode 100644 index 0000000000..7af5c989e4 --- /dev/null +++ b/test/runtime/samples/const-tag-await-then-destructuring-nested-rest/main.svelte @@ -0,0 +1,23 @@ + + +{#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]} +
{area} {volume} {perimeter}, {_width}+{_height}={sum}
+{/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]} +
{area} {volume} {perimeter}, {_width}+{_height}={sum}
+{/await} diff --git a/test/runtime/samples/const-tag-each-destructure-nested-rest/_config.js b/test/runtime/samples/const-tag-each-destructure-nested-rest/_config.js new file mode 100644 index 0000000000..00f8b31540 --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-nested-rest/_config.js @@ -0,0 +1,30 @@ +export default { + html: ` +
12 120 70, 30+4=34
+
35 350 120, 50+7=57
+
48 480 140, 60+8=68
+ `, + async test({ component, target, assert }) { + component.constant = 20; + + assert.htmlEqual(target.innerHTML, ` +
12 240 140, 60+4=64
+
35 700 240, 100+7=107
+
48 960 280, 120+8=128
+ `); + + component.boxes = [ + {width: 3, height: 4}, + {width: 4, height: 5}, + {width: 5, height: 6}, + {width: 6, height: 7} + ]; + + assert.htmlEqual(target.innerHTML, ` +
12 240 140, 60+4=64
+
20 400 180, 80+5=85
+
30 600 220, 100+6=106
+
42 840 260, 120+7=127
+ `); + } +}; diff --git a/test/runtime/samples/const-tag-each-destructure-nested-rest/main.svelte b/test/runtime/samples/const-tag-each-destructure-nested-rest/main.svelte new file mode 100644 index 0000000000..4361314b19 --- /dev/null +++ b/test/runtime/samples/const-tag-each-destructure-nested-rest/main.svelte @@ -0,0 +1,19 @@ + + +{#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]} +
{area} {volume} {perimeter}, {_width}+{_height}={sum}
+{/each} diff --git a/test/runtime/samples/destructured-props-4/A.svelte b/test/runtime/samples/destructured-props-4/A.svelte new file mode 100644 index 0000000000..ab5e6b7689 --- /dev/null +++ b/test/runtime/samples/destructured-props-4/A.svelte @@ -0,0 +1,25 @@ + + +
+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} +
+
{JSON.stringify(THING)}
diff --git a/test/runtime/samples/destructured-props-4/_config.js b/test/runtime/samples/destructured-props-4/_config.js new file mode 100644 index 0000000000..2c48ac5b3a --- /dev/null +++ b/test/runtime/samples/destructured-props-4/_config.js @@ -0,0 +1,9 @@ +export default { + html: ` +
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
+
{"a":1,"b":{"c":2,"d":[3,4,{},6,7]},"e":[6],"h":8}
+
+
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
+
{"a":1,"b":{"c":2,"d":[3,4,{},6,7]},"e":[6],"h":8}
+ ` +}; diff --git a/test/runtime/samples/destructured-props-4/main.svelte b/test/runtime/samples/destructured-props-4/main.svelte new file mode 100644 index 0000000000..cc1a31f542 --- /dev/null +++ b/test/runtime/samples/destructured-props-4/main.svelte @@ -0,0 +1,7 @@ + + + +
+
diff --git a/test/runtime/samples/destructured-props-5/A.svelte b/test/runtime/samples/destructured-props-5/A.svelte new file mode 100644 index 0000000000..898ce5aa3d --- /dev/null +++ b/test/runtime/samples/destructured-props-5/A.svelte @@ -0,0 +1,23 @@ + + +
+ 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} +
+
{JSON.stringify(LIST)}
diff --git a/test/runtime/samples/destructured-props-5/_config.js b/test/runtime/samples/destructured-props-5/_config.js new file mode 100644 index 0000000000..6c8ca89216 --- /dev/null +++ b/test/runtime/samples/destructured-props-5/_config.js @@ -0,0 +1,19 @@ +export default { + html: ` +
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
+
[1,2,3,{"a":4},[5,{},{},8]]
+
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
+
[1,2,3,{"a":4},[5,{},{},8]]
+ `, + + async test({ component, assert, target }) { + await component.update(); + + assert.htmlEqual(target.innerHTML, ` +
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
+
[1,2,3,{"a":4},[5,{},{},8]]
+
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
+
[1,2,3,{"a":4},[5,{},{},8]]
+ `); + } +}; diff --git a/test/runtime/samples/destructured-props-5/main.svelte b/test/runtime/samples/destructured-props-5/main.svelte new file mode 100644 index 0000000000..a8b9e8a704 --- /dev/null +++ b/test/runtime/samples/destructured-props-5/main.svelte @@ -0,0 +1,36 @@ + + +
+
+
diff --git a/test/runtime/samples/document-binding-fullscreen/_config.js b/test/runtime/samples/document-binding-fullscreen/_config.js new file mode 100644 index 0000000000..154ec0445a --- /dev/null +++ b/test/runtime/samples/document-binding-fullscreen/_config.js @@ -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); + } +}; diff --git a/test/runtime/samples/document-binding-fullscreen/main.svelte b/test/runtime/samples/document-binding-fullscreen/main.svelte new file mode 100644 index 0000000000..5b00199821 --- /dev/null +++ b/test/runtime/samples/document-binding-fullscreen/main.svelte @@ -0,0 +1,7 @@ + + + + +
\ No newline at end of file diff --git a/test/runtime/samples/each-block-destructured-array-nested-rest/_config.js b/test/runtime/samples/each-block-destructured-array-nested-rest/_config.js new file mode 100644 index 0000000000..5922925956 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-nested-rest/_config.js @@ -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: ` +

First: 1, Second: 2, Third: 3, Elements remaining: 2

+

First: 6, Second: 7, Third: 8, Elements remaining: 0

+

First: 9, Second: 10, Third: 11, Elements remaining: 1

+

First: 13, Second: 14, Third: 15, Elements remaining: 7

+ `, + + test({ assert, component, target }) { + component.array = [[23, 24, 25, 26, 27, 28, 29]]; + assert.htmlEqual( target.innerHTML, ` +

First: 23, Second: 24, Third: 25, Elements remaining: 4

+ `); + } +}; diff --git a/test/runtime/samples/each-block-destructured-array-nested-rest/main.svelte b/test/runtime/samples/each-block-destructured-array-nested-rest/main.svelte new file mode 100644 index 0000000000..fc73462464 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-nested-rest/main.svelte @@ -0,0 +1,9 @@ + + +{#each array as [first, second, ...[third, ...{ length }]]} +

+ First: {first}, Second: {second}, Third: {third}, Elements remaining: {length} +

+{/each} diff --git a/test/runtime/samples/window-binding-resize/_config.js b/test/runtime/samples/window-binding-resize/_config.js index c99e92a07b..d7f0282147 100644 --- a/test/runtime/samples/window-binding-resize/_config.js +++ b/test/runtime/samples/window-binding-resize/_config.js @@ -1,5 +1,5 @@ export default { - html: '
1024x768
', + html: '
1024x768
1
', before_test() { Object.defineProperties(window, { @@ -10,6 +10,10 @@ export default { innerHeight: { value: 768, configurable: true + }, + devicePixelRatio: { + value: 1, + configurable: true } }); }, @@ -27,13 +31,17 @@ export default { innerHeight: { value: 456, configurable: true + }, + devicePixelRatio: { + value: 2, + configurable: true } }); await window.dispatchEvent(event); assert.htmlEqual(target.innerHTML, ` -
567x456
+
567x456
2
`); } }; diff --git a/test/runtime/samples/window-binding-resize/main.svelte b/test/runtime/samples/window-binding-resize/main.svelte index 405f4e6e23..8ece184416 100644 --- a/test/runtime/samples/window-binding-resize/main.svelte +++ b/test/runtime/samples/window-binding-resize/main.svelte @@ -1,8 +1,10 @@ - + -
{width}x{height}
\ No newline at end of file +
{width}x{height}
+
{devicePixelRatio}
diff --git a/test/validator/samples/a11y-no-redundant-roles/input.svelte b/test/validator/samples/a11y-no-redundant-roles/input.svelte index 05525effb6..537d5c0fd3 100644 --- a/test/validator/samples/a11y-no-redundant-roles/input.svelte +++ b/test/validator/samples/a11y-no-redundant-roles/input.svelte @@ -41,4 +41,8 @@
-
\ No newline at end of file +
+ + + + diff --git a/test/validator/samples/rest-eachblock-binding-2/warnings.json b/test/validator/samples/rest-eachblock-binding-2/warnings.json index a471dd6a86..174982f21b 100644 --- a/test/validator/samples/rest-eachblock-binding-2/warnings.json +++ b/test/validator/samples/rest-eachblock-binding-2/warnings.json @@ -1,8 +1,8 @@ [ { "code": "invalid-rest-eachblock-binding", - "message": "...rest operator will create a new object and binding propagation with original object will not work", - "start": { "line": 8, "column": 24 }, + "message": "The rest operator (...) will create a new object and binding 'rest' with the original object will not work", + "start": { "line": 8, "column": 27 }, "end": { "line": 8, "column": 31 } } ] diff --git a/test/validator/samples/rest-eachblock-binding-3/warnings.json b/test/validator/samples/rest-eachblock-binding-3/warnings.json index eda7e1fc5d..3311d2afe4 100644 --- a/test/validator/samples/rest-eachblock-binding-3/warnings.json +++ b/test/validator/samples/rest-eachblock-binding-3/warnings.json @@ -1,8 +1,8 @@ [ { "code": "invalid-rest-eachblock-binding", - "message": "...rest operator will create a new object and binding propagation with original object will not work", - "start": { "line": 5, "column": 32 }, - "end": { "line": 5, "column": 39 } + "message": "The rest operator (...) will create a new object and binding 'rest' with the original object will not work", + "start": { "line": 5, "column": 35 }, + "end": { "line": 5, "column": 39 } } ] diff --git a/test/validator/samples/rest-eachblock-binding-nested-rest/input.svelte b/test/validator/samples/rest-eachblock-binding-nested-rest/input.svelte new file mode 100644 index 0000000000..31f32975f6 --- /dev/null +++ b/test/validator/samples/rest-eachblock-binding-nested-rest/input.svelte @@ -0,0 +1,9 @@ + + +{#each a as [first, second, ...[third, ...{ length }]]} +

{first}, {second}, {length}

+ + +{/each} diff --git a/test/validator/samples/rest-eachblock-binding-nested-rest/warnings.json b/test/validator/samples/rest-eachblock-binding-nested-rest/warnings.json new file mode 100644 index 0000000000..d935275edf --- /dev/null +++ b/test/validator/samples/rest-eachblock-binding-nested-rest/warnings.json @@ -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 + } + } +] diff --git a/test/validator/samples/rest-eachblock-binding/warnings.json b/test/validator/samples/rest-eachblock-binding/warnings.json index 35fb2d0b6e..992e8880d9 100644 --- a/test/validator/samples/rest-eachblock-binding/warnings.json +++ b/test/validator/samples/rest-eachblock-binding/warnings.json @@ -1,8 +1,8 @@ [ { "code": "invalid-rest-eachblock-binding", - "message": "...rest operator will create a new object and binding propagation with original object will not work", - "start": { "line": 5, "column": 25 }, + "message": "The rest operator (...) will create a new object and binding 'rest' with the original object will not work", + "start": { "line": 5, "column": 28 }, "end": { "line": 5, "column": 32 } } ] diff --git a/test/validator/samples/window-binding-invalid/errors.json b/test/validator/samples/window-binding-invalid/errors.json index 1277984258..04ecbaafcd 100644 --- a/test/validator/samples/window-binding-invalid/errors.json +++ b/test/validator/samples/window-binding-invalid/errors.json @@ -1,6 +1,6 @@ [{ "code": "invalid-binding", - "message": "'potato' is not a valid binding on — valid bindings are innerWidth, innerHeight, outerWidth, outerHeight, scrollX, scrollY or online", + "message": "'potato' is not a valid binding on — valid bindings are innerWidth, innerHeight, outerWidth, outerHeight, scrollX, scrollY, devicePixelRatio or online", "start": { "line": 1, "column": 15