From 7d1e4e82ffb5825bee254113cbba360aea0745f0 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 15 Mar 2020 18:12:52 +0800 Subject: [PATCH] support destructuring in await (#4548) --- CHANGELOG.md | 1 + src/compiler/compile/nodes/AwaitBlock.ts | 28 ++++++-- src/compiler/compile/nodes/CatchBlock.ts | 11 +++- src/compiler/compile/nodes/ThenBlock.ts | 11 +++- src/compiler/compile/render_dom/Block.ts | 4 ++ .../compile/render_dom/wrappers/AwaitBlock.ts | 65 ++++++++++++++----- .../compile/render_ssr/handlers/AwaitBlock.ts | 2 +- .../utils/traverse_destructure_pattern.ts | 35 ++++++++++ src/compiler/parse/index.ts | 48 ++++++++++++++ src/compiler/parse/state/mustache.ts | 4 +- src/compiler/parse/utils/bracket.ts | 28 ++++++++ src/compiler/utils/error.ts | 2 +- test/parser/samples/await-catch/output.json | 5 +- .../samples/await-then-catch/output.json | 10 ++- .../no-error-if-before-closing/output.json | 10 ++- .../await-then-destruct-array/_config.js | 61 +++++++++++++++++ .../await-then-destruct-array/main.svelte | 14 ++++ .../await-then-destruct-default/_config.js | 23 +++++++ .../await-then-destruct-default/main.svelte | 34 ++++++++++ .../await-then-destruct-object/_config.js | 63 ++++++++++++++++++ .../await-then-destruct-object/main.svelte | 13 ++++ .../await-then-destruct-rest/_config.js | 21 ++++++ .../await-then-destruct-rest/main.svelte | 32 +++++++++ 23 files changed, 489 insertions(+), 36 deletions(-) create mode 100644 src/compiler/compile/utils/traverse_destructure_pattern.ts create mode 100644 src/compiler/parse/utils/bracket.ts create mode 100644 test/runtime/samples/await-then-destruct-array/_config.js create mode 100644 test/runtime/samples/await-then-destruct-array/main.svelte create mode 100644 test/runtime/samples/await-then-destruct-default/_config.js create mode 100644 test/runtime/samples/await-then-destruct-default/main.svelte create mode 100644 test/runtime/samples/await-then-destruct-object/_config.js create mode 100644 test/runtime/samples/await-then-destruct-object/main.svelte create mode 100644 test/runtime/samples/await-then-destruct-rest/_config.js create mode 100644 test/runtime/samples/await-then-destruct-rest/main.svelte diff --git a/CHANGELOG.md b/CHANGELOG.md index b85ec4ba16..a24223e5bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +* Allow destructuring in `{#await}` blocks ([#1851](https://github.com/sveltejs/svelte/issues/1851)) * Allow `` to be used in a slot ([#2798](https://github.com/sveltejs/svelte/issues/2798)) * Expose object of unknown props in `$$restProps` ([#2930](https://github.com/sveltejs/svelte/issues/2930)) * Prevent passing named slots other than from the top level within a component ([#3385](https://github.com/sveltejs/svelte/issues/3385)) diff --git a/src/compiler/compile/nodes/AwaitBlock.ts b/src/compiler/compile/nodes/AwaitBlock.ts index cd57750d29..a7b8fb815e 100644 --- a/src/compiler/compile/nodes/AwaitBlock.ts +++ b/src/compiler/compile/nodes/AwaitBlock.ts @@ -3,27 +3,45 @@ import PendingBlock from './PendingBlock'; import ThenBlock from './ThenBlock'; import CatchBlock from './CatchBlock'; import Expression from './shared/Expression'; +import { Pattern } from 'estree'; +import Component from '../Component'; +import TemplateScope from './shared/TemplateScope'; +import { TemplateNode } from '../../interfaces'; +import traverse_destructure_pattern from '../utils/traverse_destructure_pattern'; export default class AwaitBlock extends Node { type: 'AwaitBlock'; expression: Expression; - value: string; - error: string; + value: DestructurePattern; + error: DestructurePattern; pending: PendingBlock; then: ThenBlock; catch: CatchBlock; - constructor(component, parent, scope, info) { + constructor(component: Component, parent, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); this.expression = new Expression(component, this, scope, info.expression); - this.value = info.value; - this.error = info.error; + this.value = info.value && new DestructurePattern(info.value); + this.error = info.error && new DestructurePattern(info.error); this.pending = new PendingBlock(component, this, scope, info.pending); this.then = new ThenBlock(component, this, scope, info.then); this.catch = new CatchBlock(component, this, scope, info.catch); } } + +export class DestructurePattern { + pattern: Pattern; + expressions: string[]; + identifier_name: string | undefined; + + constructor(pattern: Pattern) { + this.pattern = pattern; + this.expressions = []; + traverse_destructure_pattern(pattern, (node) => this.expressions.push(node.name)); + this.identifier_name = this.pattern.type === 'Identifier' ? this.pattern.name : undefined; + } +} diff --git a/src/compiler/compile/nodes/CatchBlock.ts b/src/compiler/compile/nodes/CatchBlock.ts index 0edc0f76d8..8b3736a2b9 100644 --- a/src/compiler/compile/nodes/CatchBlock.ts +++ b/src/compiler/compile/nodes/CatchBlock.ts @@ -1,16 +1,23 @@ import map_children from './shared/map_children'; import TemplateScope from './shared/TemplateScope'; import AbstractBlock from './shared/AbstractBlock'; +import AwaitBlock from './AwaitBlock'; +import Component from '../Component'; +import { TemplateNode } from '../../interfaces'; export default class CatchBlock extends AbstractBlock { type: 'CatchBlock'; scope: TemplateScope; - constructor(component, parent, scope, info) { + constructor(component: Component, parent: AwaitBlock, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); this.scope = scope.child(); - this.scope.add(parent.error, parent.expression.dependencies, this); + if (parent.error) { + parent.error.expressions.forEach(expression => { + this.scope.add(expression, parent.expression.dependencies, this); + }); + } this.children = map_children(component, parent, this.scope, info.children); if (!info.skip) { diff --git a/src/compiler/compile/nodes/ThenBlock.ts b/src/compiler/compile/nodes/ThenBlock.ts index 7f9bbde0f0..7eefe2e6fb 100644 --- a/src/compiler/compile/nodes/ThenBlock.ts +++ b/src/compiler/compile/nodes/ThenBlock.ts @@ -1,16 +1,23 @@ import map_children from './shared/map_children'; import TemplateScope from './shared/TemplateScope'; import AbstractBlock from './shared/AbstractBlock'; +import AwaitBlock from './AwaitBlock'; +import Component from '../Component'; +import { TemplateNode } from '../../interfaces'; export default class ThenBlock extends AbstractBlock { type: 'ThenBlock'; scope: TemplateScope; - constructor(component, parent, scope, info) { + constructor(component: Component, parent: AwaitBlock, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); this.scope = scope.child(); - this.scope.add(parent.value, parent.expression.dependencies, this); + if (parent.value) { + parent.value.expressions.forEach(expression => { + this.scope.add(expression, parent.expression.dependencies, this); + }); + } this.children = map_children(component, parent, this.scope, info.children); if (!info.skip) { diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 2da77d3fbf..3deba786b4 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -46,6 +46,7 @@ export default class Block { }>; chunks: { + declarations: Array; init: Array; create: Array; claim: Array; @@ -93,6 +94,7 @@ export default class Block { this.bindings = options.bindings; this.chunks = { + declarations: [], init: [], create: [], claim: [], @@ -384,6 +386,8 @@ export default class Block { const block = dev && this.get_unique_name('block'); const body = b` + ${this.chunks.declarations} + ${Array.from(this.variables.values()).map(({ id, init }) => { return init ? b`let ${id} = ${init}` diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index 12b7fc2cef..d6ac17f0ce 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -9,6 +9,7 @@ import PendingBlock from '../../nodes/PendingBlock'; import ThenBlock from '../../nodes/ThenBlock'; import CatchBlock from '../../nodes/CatchBlock'; import { Identifier } from 'estree'; +import traverse_destructure_pattern from '../../utils/traverse_destructure_pattern'; class AwaitBlockBranch extends Wrapper { node: PendingBlock | ThenBlock | CatchBlock; @@ -46,6 +47,23 @@ class AwaitBlockBranch extends Wrapper { this.is_dynamic = this.block.dependencies.size > 0; } + + render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + this.fragment.render(block, parent_node, parent_nodes); + } + + render_destructure(block: Block, value, node, index) { + if (value && node.pattern.type !== 'Identifier') { + traverse_destructure_pattern(node.pattern, (node, parent, index) => { + parent[index] = x`#ctx[${block.renderer.context_lookup.get(node.name).index}]`; + }); + + this.block.chunks.declarations.push(b`(${node.pattern} = #ctx[${index}])`); + if (this.block.has_update_method) { + this.block.chunks.update.push(b`(${node.pattern} = #ctx[${index}])`); + } + } + } } export default class AwaitBlockWrapper extends Wrapper { @@ -55,6 +73,9 @@ export default class AwaitBlockWrapper extends Wrapper { then: AwaitBlockBranch; catch: AwaitBlockBranch; + value: string; + error: string; + var: Identifier = { type: 'Identifier', name: 'await_block' }; constructor( @@ -71,8 +92,20 @@ export default class AwaitBlockWrapper extends Wrapper { this.not_static_content(); block.add_dependencies(this.node.expression.dependencies); - if (this.node.value) block.renderer.add_to_context(this.node.value, true); - if (this.node.error) block.renderer.add_to_context(this.node.error, true); + if (this.node.value) { + for (const ctx of this.node.value.expressions) { + block.renderer.add_to_context(ctx, true); + } + this.value = this.node.value.identifier_name || block.get_unique_name('value').name; + block.renderer.add_to_context(this.value, true); + } + if (this.node.error) { + for (const ctx of this.node.error.expressions) { + block.renderer.add_to_context(ctx, true); + } + this.error = this.node.error.identifier_name || block.get_unique_name('error').name; + block.renderer.add_to_context(this.error, true); + } let is_dynamic = false; let has_intros = false; @@ -105,17 +138,11 @@ export default class AwaitBlockWrapper extends Wrapper { this[status] = branch; }); - this.pending.block.has_update_method = is_dynamic; - this.then.block.has_update_method = is_dynamic; - this.catch.block.has_update_method = is_dynamic; - - this.pending.block.has_intro_method = has_intros; - this.then.block.has_intro_method = has_intros; - this.catch.block.has_intro_method = has_intros; - - this.pending.block.has_outro_method = has_outros; - this.then.block.has_outro_method = has_outros; - this.catch.block.has_outro_method = has_outros; + ['pending', 'then', 'catch'].forEach(status => { + this[status].block.has_update_method = is_dynamic; + this[status].block.has_intro_method = has_intros; + this[status].block.has_outro_method = has_outros; + }); if (has_outros) { block.add_outro(); @@ -139,8 +166,8 @@ export default class AwaitBlockWrapper extends Wrapper { block.maintain_context = true; - const value_index = this.node.value && block.renderer.context_lookup.get(this.node.value).index; - const error_index = this.node.error && block.renderer.context_lookup.get(this.node.error).index; + const value_index = this.value && block.renderer.context_lookup.get(this.value).index; + const error_index = this.error && block.renderer.context_lookup.get(this.error).index; const info_props: any = x`{ ctx: #ctx, @@ -205,7 +232,7 @@ export default class AwaitBlockWrapper extends Wrapper { } else { const #child_ctx = #ctx.slice(); - ${this.node.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} + ${this.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} ${info}.block.p(#child_ctx, #dirty); } `); @@ -219,7 +246,7 @@ export default class AwaitBlockWrapper extends Wrapper { block.chunks.update.push(b` { const #child_ctx = #ctx.slice(); - ${this.node.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} + ${this.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} ${info}.block.p(#child_ctx, #dirty); } `); @@ -242,7 +269,9 @@ export default class AwaitBlockWrapper extends Wrapper { `); [this.pending, this.then, this.catch].forEach(branch => { - branch.fragment.render(branch.block, null, x`#nodes` as Identifier); + branch.render(branch.block, null, x`#nodes` as Identifier); }); + this.then.render_destructure(block, this.value, this.node.value, value_index); + this.catch.render_destructure(block, this.error, this.node.error, error_index); } } diff --git a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts index f637826280..d6bb86f1a4 100644 --- a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts +++ b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts @@ -14,7 +14,7 @@ export default function(node: AwaitBlock, renderer: Renderer, options: RenderOpt renderer.add_expression(x` function(__value) { if (@is_promise(__value)) return ${pending}; - return (function(${node.value}) { return ${then}; }(__value)); + return (function(${node.value ? node.value.pattern : ''}) { return ${then}; }(__value)); }(${node.expression.node}) `); } diff --git a/src/compiler/compile/utils/traverse_destructure_pattern.ts b/src/compiler/compile/utils/traverse_destructure_pattern.ts new file mode 100644 index 0000000000..6c918c28c0 --- /dev/null +++ b/src/compiler/compile/utils/traverse_destructure_pattern.ts @@ -0,0 +1,35 @@ +import { Pattern, Identifier, RestElement } from "estree"; +import { Node } from "acorn"; + +export default function traverse_destructure_pattern( + node: Pattern, + callback: (node: Identifier, parent: Node, key: string | number) => void +) { + function traverse(node: Pattern, parent, key) { + switch (node.type) { + case "Identifier": + return callback(node, parent, key); + case "ArrayPattern": + for (let i = 0; i < node.elements.length; i++) { + const element = node.elements[i]; + traverse(element, node.elements, i); + } + break; + case "ObjectPattern": + for (let i = 0; i < node.properties.length; i++) { + const property = node.properties[i]; + if (property.type === "Property") { + traverse(property.value, property, "value"); + } else { + traverse((property as any) as RestElement, node.properties, i); + } + } + break; + case "RestElement": + return traverse(node.argument, node, 'argument'); + case "AssignmentPattern": + return traverse(node.left, node, 'left'); + } + } + traverse(node, null, null); +} diff --git a/src/compiler/parse/index.ts b/src/compiler/parse/index.ts index a809eeebeb..c21e6d6f79 100644 --- a/src/compiler/parse/index.ts +++ b/src/compiler/parse/index.ts @@ -5,6 +5,9 @@ import { reserved } from '../utils/names'; import full_char_code_at from '../utils/full_char_code_at'; import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces'; import error from '../utils/error'; +import { is_bracket_open, is_bracket_close, is_bracket_pair, get_bracket_close } from './utils/bracket'; +import { parse_expression_at } from './acorn'; +import { Pattern } from 'estree'; type ParserState = (parser: Parser) => (ParserState | void); @@ -170,6 +173,51 @@ export class Parser { return identifier; } + read_destructure_pattern(): Pattern { + const start = this.index; + let i = this.index; + + const code = full_char_code_at(this.template, i); + if (isIdentifierStart(code, true)) { + return { type: 'Identifier', name: this.read_identifier() }; + } + + if (!is_bracket_open(code)) { + this.error({ + code: 'unexpected-token', + message: 'Expected identifier or destructure pattern', + }); + } + + const bracket_stack = [code]; + i += code <= 0xffff ? 1 : 2; + + while (i < this.template.length) { + const code = full_char_code_at(this.template, i); + if (is_bracket_open(code)) { + bracket_stack.push(code); + } else if (is_bracket_close(code)) { + if (!is_bracket_pair(bracket_stack[bracket_stack.length - 1], code)) { + this.error({ + code: 'unexpected-token', + message: `Expected ${String.fromCharCode(get_bracket_close(bracket_stack[bracket_stack.length - 1]))}` + }); + } + bracket_stack.pop(); + if (bracket_stack.length === 0) { + i += code <= 0xffff ? 1 : 2; + break; + } + } + i += code <= 0xffff ? 1 : 2; + } + + this.index = i; + + const pattern_string = this.template.slice(start, i); + return (parse_expression_at(`(${pattern_string} = 1)`, 0) as any).left as Pattern; + } + read_until(pattern: RegExp) { if (this.index >= this.template.length) this.error({ diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts index e5e365dddf..28ef853f8e 100644 --- a/src/compiler/parse/state/mustache.ts +++ b/src/compiler/parse/state/mustache.ts @@ -196,7 +196,7 @@ export default function mustache(parser: Parser) { if (!parser.eat('}')) { parser.require_whitespace(); - await_block[is_then ? 'value': 'error'] = parser.read_identifier(); + await_block[is_then ? 'value': 'error'] = parser.read_destructure_pattern(); parser.allow_whitespace(); parser.eat('}', true); } @@ -305,7 +305,7 @@ export default function mustache(parser: Parser) { const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then'); if (await_block_shorthand) { parser.require_whitespace(); - block.value = parser.read_identifier(); + block.value = parser.read_destructure_pattern(); parser.allow_whitespace(); } diff --git a/src/compiler/parse/utils/bracket.ts b/src/compiler/parse/utils/bracket.ts new file mode 100644 index 0000000000..ffbb9e4952 --- /dev/null +++ b/src/compiler/parse/utils/bracket.ts @@ -0,0 +1,28 @@ +const SQUARE_BRACKET_OPEN = "[".charCodeAt(0); +const SQUARE_BRACKET_CLOSE = "]".charCodeAt(0); +const CURLY_BRACKET_OPEN = "{".charCodeAt(0); +const CURLY_BRACKET_CLOSE = "}".charCodeAt(0); + +export function is_bracket_open(code) { + return code === SQUARE_BRACKET_OPEN || code === CURLY_BRACKET_OPEN; +} + +export function is_bracket_close(code) { + return code === SQUARE_BRACKET_CLOSE || code === CURLY_BRACKET_CLOSE; +} + +export function is_bracket_pair(open, close) { + return ( + (open === SQUARE_BRACKET_OPEN && close === SQUARE_BRACKET_CLOSE) || + (open === CURLY_BRACKET_OPEN && close === CURLY_BRACKET_CLOSE) + ); +} + +export function get_bracket_close(open) { + if (open === SQUARE_BRACKET_OPEN) { + return SQUARE_BRACKET_CLOSE; + } + if (open === CURLY_BRACKET_OPEN) { + return CURLY_BRACKET_CLOSE; + } +} \ No newline at end of file diff --git a/src/compiler/utils/error.ts b/src/compiler/utils/error.ts index d13222578a..e05ea66df1 100644 --- a/src/compiler/utils/error.ts +++ b/src/compiler/utils/error.ts @@ -21,7 +21,7 @@ export default function error(message: string, props: { filename: string; start: number; end?: number; -}) { +}): never { const error = new CompileError(message); error.name = props.name; diff --git a/test/parser/samples/await-catch/output.json b/test/parser/samples/await-catch/output.json index 2461f467f2..d47d27e4cb 100644 --- a/test/parser/samples/await-catch/output.json +++ b/test/parser/samples/await-catch/output.json @@ -25,7 +25,10 @@ "name": "thePromise" }, "value": null, - "error": "theError", + "error": { + "type": "Identifier", + "name": "theError" + }, "pending": { "start": 19, "end": 39, diff --git a/test/parser/samples/await-then-catch/output.json b/test/parser/samples/await-then-catch/output.json index d9defb6932..01dc89f3f9 100644 --- a/test/parser/samples/await-then-catch/output.json +++ b/test/parser/samples/await-then-catch/output.json @@ -24,8 +24,14 @@ }, "name": "thePromise" }, - "value": "theValue", - "error": "theError", + "value": { + "type": "Identifier", + "name": "theValue" + }, + "error": { + "type": "Identifier", + "name": "theError" + }, "pending": { "start": 19, "end": 39, diff --git a/test/parser/samples/no-error-if-before-closing/output.json b/test/parser/samples/no-error-if-before-closing/output.json index 251cefc0ca..e30c302e0f 100644 --- a/test/parser/samples/no-error-if-before-closing/output.json +++ b/test/parser/samples/no-error-if-before-closing/output.json @@ -115,7 +115,10 @@ "value": true, "raw": "true" }, - "value": "f", + "value": { + "type": "Identifier", + "name": "f" + }, "error": null, "pending": { "start": 80, @@ -198,7 +201,10 @@ "value": true, "raw": "true" }, - "value": "f", + "value": { + "type": "Identifier", + "name": "f" + }, "error": null, "pending": { "start": 123, diff --git a/test/runtime/samples/await-then-destruct-array/_config.js b/test/runtime/samples/await-then-destruct-array/_config.js new file mode 100644 index 0000000000..55c48481e0 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-array/_config.js @@ -0,0 +1,61 @@ +export default { + props: { + thePromise: new Promise(resolve => {}) + }, + + html: ` + loading... + `, + + async test({ assert, component, target }) { + await (component.thePromise = Promise.resolve([1, 2])); + + assert.htmlEqual( + target.innerHTML, + ` +

a: 1

+

b: 2

+ ` + ); + + await (component.thePromise = Promise.resolve([4, 5])); + + assert.htmlEqual( + target.innerHTML, + ` +

a: 4

+

b: 5

+ ` + ); + + try { + await (component.thePromise = Promise.reject(['a', [6, 7]])); + } catch (e) { + // do nothing + } + + assert.htmlEqual( + target.innerHTML, + ` +

c: a

+

d: 6

+

e: 7

+ ` + ); + + try { + await (component.thePromise = Promise.reject(['b', [8, 9]])); + } catch (e) { + // do nothing + } + + assert.htmlEqual( + target.innerHTML, + ` +

c: b

+

d: 8

+

e: 9

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

a: {a}

+

b: {b}

+{:catch [c, [d, e]]} +

c: {c}

+

d: {d}

+

e: {e}

+{/await} \ No newline at end of file diff --git a/test/runtime/samples/await-then-destruct-default/_config.js b/test/runtime/samples/await-then-destruct-default/_config.js new file mode 100644 index 0000000000..d0e5a49f28 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-default/_config.js @@ -0,0 +1,23 @@ +export default { + async test({ assert, component, target }) { + await Promise.resolve(); + + assert.htmlEqual( + target.innerHTML, + ` +

a: 3

+

b: 2

+

c: 3

+

a: 1

+

b: 2

+

c: 3

+

a: 3

+

b: 2

+

c: 3

+

a: 1

+

b: 2

+

c: 3

+ ` + ); + } +}; diff --git a/test/runtime/samples/await-then-destruct-default/main.svelte b/test/runtime/samples/await-then-destruct-default/main.svelte new file mode 100644 index 0000000000..d559aadab1 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-default/main.svelte @@ -0,0 +1,34 @@ + + +{#await object then { a = 3, b = 4, c }} +

a: {a}

+

b: {b}

+

c: {c}

+{/await} + +{#await array then [a, b, c = 3]} +

a: {a}

+

b: {b}

+

c: {c}

+{/await} + +{#await objectReject then value} + resolved +{:catch { a = 3, b = 4, c }} +

a: {a}

+

b: {b}

+

c: {c}

+{/await} + +{#await arrayReject then value} + resolved +{:catch [a, b, c = 3]} +

a: {a}

+

b: {b}

+

c: {c}

+{/await} \ No newline at end of file diff --git a/test/runtime/samples/await-then-destruct-object/_config.js b/test/runtime/samples/await-then-destruct-object/_config.js new file mode 100644 index 0000000000..371462de97 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-object/_config.js @@ -0,0 +1,63 @@ +export default { + props: { + thePromise: new Promise(resolve => {}) + }, + + html: ` + loading... + `, + + async test({ assert, component, target }) { + await (component.thePromise = Promise.resolve({ error: "error message" })); + + assert.htmlEqual( + target.innerHTML, + ` +

error: error message

+

result: undefined

+ ` + ); + + await (component.thePromise = Promise.resolve({ result: "42" })); + + assert.htmlEqual( + target.innerHTML, + ` +

error: undefined

+

result: 42

+ ` + ); + + try { + await (component.thePromise = Promise.reject({ + error: { message: "oops", code: "123" } + })); + } catch (e) { + // do nothing + } + + assert.htmlEqual( + target.innerHTML, + ` +

message: oops

+

code: 123

+ ` + ); + + try { + await (component.thePromise = Promise.reject({ + error: { message: "timeout", code: "456" } + })); + } catch (e) { + // do nothing + } + + assert.htmlEqual( + target.innerHTML, + ` +

message: timeout

+

code: 456

+ ` + ); + } +}; diff --git a/test/runtime/samples/await-then-destruct-object/main.svelte b/test/runtime/samples/await-then-destruct-object/main.svelte new file mode 100644 index 0000000000..ff574b7be8 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-object/main.svelte @@ -0,0 +1,13 @@ + + +{#await thePromise} + loading... +{:then { result, error }} +

error: {error}

+

result: {result}

+{:catch { error: { message, code } }} +

message: {message}

+

code: {code}

+{/await} \ No newline at end of file diff --git a/test/runtime/samples/await-then-destruct-rest/_config.js b/test/runtime/samples/await-then-destruct-rest/_config.js new file mode 100644 index 0000000000..528568e569 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-rest/_config.js @@ -0,0 +1,21 @@ +export default { + async test({ assert, component, target }) { + await Promise.resolve(); + + assert.htmlEqual( + target.innerHTML, + ` +

a: 1

+

rest: {"b":2,"c":3}

+

a: 1

+

b: 2

+

rest: [3,4,5,6]

+

a: 1

+

rest: {"b":2,"c":3}

+

a: 1

+

b: 2

+

rest: [3,4,5,6]

+ ` + ); + } +}; diff --git a/test/runtime/samples/await-then-destruct-rest/main.svelte b/test/runtime/samples/await-then-destruct-rest/main.svelte new file mode 100644 index 0000000000..a2d57dabf3 --- /dev/null +++ b/test/runtime/samples/await-then-destruct-rest/main.svelte @@ -0,0 +1,32 @@ + + +{#await object then { a, ...rest }} +

a: {a}

+

rest: {JSON.stringify(rest)}

+{/await} + +{#await array then [a, b, ...rest]} +

a: {a}

+

b: {b}

+

rest: {JSON.stringify(rest)}

+{/await} + +{#await objectReject then value} + resolved +{:catch { a, ...rest }} +

a: {a}

+

rest: {JSON.stringify(rest)}

+{/await} + +{#await arrayReject then value} + resolved +{:catch [a, b, ...rest]} +

a: {a}

+

b: {b}

+

rest: {JSON.stringify(rest)}

+{/await} \ No newline at end of file