diff --git a/src/compile/dom/Block.ts b/src/compile/dom/Block.ts index f41892ccc93d..11f34268b391 100644 --- a/src/compile/dom/Block.ts +++ b/src/compile/dom/Block.ts @@ -9,8 +9,7 @@ export interface BlockOptions { compiler?: Compiler; comment?: string; key?: string; - indexNames?: Map; - listNames?: Map; + bindings?: Map; dependencies?: Set; } @@ -23,8 +22,8 @@ export default class Block { first: string; dependencies: Set; - indexNames: Map; - listNames: Map; + + bindings: Map; builders: { init: CodeBuilder; @@ -62,8 +61,7 @@ export default class Block { this.dependencies = new Set(); - this.indexNames = options.indexNames; - this.listNames = options.listNames; + this.bindings = options.bindings; this.builders = { init: new CodeBuilder(), diff --git a/src/compile/nodes/Binding.ts b/src/compile/nodes/Binding.ts index 3c56da0f437a..c282cf1a9d9d 100644 --- a/src/compile/nodes/Binding.ts +++ b/src/compile/nodes/Binding.ts @@ -195,14 +195,13 @@ function getEventHandler( ? getTailSnippet(binding.value.node) : ''; - const list = `ctx.${block.listNames.get(name)}`; - const index = `ctx.${block.indexNames.get(name)}`; + const head = block.bindings.get(name); return { usesContext: true, usesState: true, usesStore: storeDependencies.length > 0, - mutation: `${list}[${index}]${tail} = ${value};`, + mutation: `${head}${tail} = ${value};`, props: dependencies.map(prop => `${prop}: ctx.${prop}`), storeProps: storeDependencies.map(prop => `${prop}: $.${prop}`) }; diff --git a/src/compile/nodes/Component.ts b/src/compile/nodes/Component.ts index d40466a05202..1a872fe6cea7 100644 --- a/src/compile/nodes/Component.ts +++ b/src/compile/nodes/Component.ts @@ -239,12 +239,11 @@ export default class Component extends Node { const computed = isComputed(binding.value.node); const tail = binding.value.node.type === 'MemberExpression' ? getTailSnippet(binding.value.node) : ''; - const list = block.listNames.get(key); - const index = block.indexNames.get(key); + const head = block.bindings.get(key); const lhs = binding.value.node.type === 'MemberExpression' ? binding.value.snippet - : `ctx.${list}[ctx.${index}]${tail} = childState.${binding.name}`; + : `${head}${tail} = childState.${binding.name}`; setFromChild = deindent` ${lhs} = childState.${binding.name}; diff --git a/src/compile/nodes/EachBlock.ts b/src/compile/nodes/EachBlock.ts index 6d14cb6e007a..6d771dbf879f 100644 --- a/src/compile/nodes/EachBlock.ts +++ b/src/compile/nodes/EachBlock.ts @@ -6,6 +6,7 @@ import createDebuggingComment from '../../utils/createDebuggingComment'; import Expression from './shared/Expression'; import mapChildren from './shared/mapChildren'; import TemplateScope from './shared/TemplateScope'; +import unpackDestructuring from '../../utils/unpackDestructuring'; export default class EachBlock extends Node { type: 'EachBlock'; @@ -18,7 +19,7 @@ export default class EachBlock extends Node { context: string; key: Expression; scope: TemplateScope; - destructuredContexts: string[]; + contexts: Array<{ name: string, tail: string }>; children: Node[]; else?: ElseBlock; @@ -27,7 +28,7 @@ export default class EachBlock extends Node { super(compiler, parent, scope, info); this.expression = new Expression(compiler, this, scope, info.expression); - this.context = info.context; + this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring this.index = info.index; this.key = info.key @@ -36,7 +37,12 @@ export default class EachBlock extends Node { this.scope = scope.child(); - this.scope.add(this.context, this.expression.dependencies); + this.contexts = []; + unpackDestructuring(this.contexts, info.context, ''); + + this.contexts.forEach(context => { + this.scope.add(context.key.name, this.expression.dependencies); + }); if (this.index) { // index can only change if this is a keyed each block @@ -44,12 +50,6 @@ export default class EachBlock extends Node { this.scope.add(this.index, dependencies); } - // TODO more general approach to destructuring - this.destructuredContexts = info.destructuredContexts || []; - this.destructuredContexts.forEach(name => { - this.scope.add(name, this.expression.dependencies); - }); - this.children = mapChildren(compiler, this, this.scope, info.children); this.else = info.else @@ -76,31 +76,28 @@ export default class EachBlock extends Node { name: this.compiler.getUniqueName('create_each_block'), key: this.key, - indexNames: new Map(block.indexNames), - listNames: new Map(block.listNames) + bindings: new Map(block.bindings) }); - const listName = this.compiler.getUniqueName('each_value'); + this.each_block_value = this.compiler.getUniqueName('each_value'); + const indexName = this.index || this.compiler.getUniqueName(`${this.context}_index`); - this.block.indexNames.set(this.context, indexName); - this.block.listNames.set(this.context, listName); + this.contexts.forEach(prop => { + this.block.bindings.set(prop.key.name, `ctx.${this.each_block_value}[ctx.${indexName}]${prop.tail}`); + }); if (this.index) { this.block.getUniqueName(this.index); // this prevents name collisions (#1254) } - this.contextProps = [ - `${listName}: list`, - `${this.context}: list[i]`, - `${indexName}: i` - ]; + this.contextProps = this.contexts.map(prop => `${prop.key.name}: list[i]${prop.tail}`); - if (this.destructuredContexts) { - for (let i = 0; i < this.destructuredContexts.length; i += 1) { - this.contextProps.push(`${this.destructuredContexts[i]}: list[i][${i}]`); - } - } + // TODO only add these if necessary + this.contextProps.push( + `${this.each_block_value}: list`, + `${indexName}: i` + ); this.compiler.target.blocks.push(this.block); this.initChildren(this.block, stripWhitespace, nextSibling); @@ -135,7 +132,6 @@ export default class EachBlock extends Node { const each = this.var; const create_each_block = this.block.name; - const each_block_value = this.block.listNames.get(this.context); const iterations = this.iterations; const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode(); @@ -154,7 +150,6 @@ export default class EachBlock extends Node { const vars = { each, create_each_block, - each_block_value, length, iterations, anchor, @@ -163,7 +158,7 @@ export default class EachBlock extends Node { const { snippet } = this.expression; - block.builders.init.addLine(`var ${each_block_value} = ${snippet};`); + block.builders.init.addLine(`var ${this.each_block_value} = ${snippet};`); this.compiler.target.blocks.push(deindent` function ${this.get_each_context}(ctx, list, i) { @@ -195,7 +190,7 @@ export default class EachBlock extends Node { // TODO neaten this up... will end up with an empty line in the block block.builders.init.addBlock(deindent` - if (!${each_block_value}.${length}) { + if (!${this.each_block_value}.${length}) { ${each_block_else} = ${this.else.block.name}(#component, ctx); ${each_block_else}.c(); } @@ -211,9 +206,9 @@ export default class EachBlock extends Node { if (this.else.block.hasUpdateMethod) { block.builders.update.addBlock(deindent` - if (!${each_block_value}.${length} && ${each_block_else}) { + if (!${this.each_block_value}.${length} && ${each_block_else}) { ${each_block_else}.p(changed, ctx); - } else if (!${each_block_value}.${length}) { + } else if (!${this.each_block_value}.${length}) { ${each_block_else} = ${this.else.block.name}(#component, ctx); ${each_block_else}.c(); ${each_block_else}.${mountOrIntro}(${initialMountNode}, ${anchor}); @@ -225,7 +220,7 @@ export default class EachBlock extends Node { `); } else { block.builders.update.addBlock(deindent` - if (${each_block_value}.${length}) { + if (${this.each_block_value}.${length}) { if (${each_block_else}) { ${each_block_else}.u(); ${each_block_else}.d(); @@ -267,7 +262,6 @@ export default class EachBlock extends Node { { each, create_each_block, - each_block_value, length, anchor, mountOrIntro, @@ -295,8 +289,8 @@ export default class EachBlock extends Node { block.builders.init.addBlock(deindent` const ${get_key} = ctx => ${this.key.snippet}; - for (var #i = 0; #i < ${each_block_value}.${length}; #i += 1) { - let child_ctx = ${this.get_each_context}(ctx, ${each_block_value}, #i); + for (var #i = 0; #i < ${this.each_block_value}.${length}; #i += 1) { + let child_ctx = ${this.get_each_context}(ctx, ${this.each_block_value}, #i); let key = ${get_key}(child_ctx); ${blocks}[#i] = ${lookup}[key] = ${create_each_block}(#component, key, child_ctx); } @@ -323,9 +317,9 @@ export default class EachBlock extends Node { const dynamic = this.block.hasUpdateMethod; block.builders.update.addBlock(deindent` - var ${each_block_value} = ${snippet}; + var ${this.each_block_value} = ${snippet}; - ${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${each_block_value}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.get_each_context}); + ${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.each_block_value}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.get_each_context}); `); if (!parentNode) { @@ -346,7 +340,6 @@ export default class EachBlock extends Node { snippet: string, { create_each_block, - each_block_value, length, iterations, anchor, @@ -356,8 +349,8 @@ export default class EachBlock extends Node { block.builders.init.addBlock(deindent` var ${iterations} = []; - for (var #i = 0; #i < ${each_block_value}.${length}; #i += 1) { - ${iterations}[#i] = ${create_each_block}(#component, ${this.get_each_context}(ctx, ${each_block_value}, #i)); + for (var #i = 0; #i < ${this.each_block_value}.${length}; #i += 1) { + ${iterations}[#i] = ${create_each_block}(#component, ${this.get_each_context}(ctx, ${this.each_block_value}, #i)); } `); @@ -445,15 +438,15 @@ export default class EachBlock extends Node { ${iterations}[#i].u(); ${iterations}[#i].d(); } - ${iterations}.length = ${each_block_value}.${length}; + ${iterations}.length = ${this.each_block_value}.${length}; `; block.builders.update.addBlock(deindent` if (${condition}) { - ${each_block_value} = ${snippet}; + ${this.each_block_value} = ${snippet}; - for (var #i = ${start}; #i < ${each_block_value}.${length}; #i += 1) { - const child_ctx = ${this.get_each_context}(ctx, ${each_block_value}, #i); + for (var #i = ${start}; #i < ${this.each_block_value}.${length}; #i += 1) { + const child_ctx = ${this.get_each_context}(ctx, ${this.each_block_value}, #i); ${forLoopBody} } @@ -481,8 +474,7 @@ export default class EachBlock extends Node { const { compiler } = this; const { snippet } = this.expression; - const props = [`${this.context}: item`] - .concat(this.destructuredContexts.map((name, i) => `${name}: item[${i}]`)); + const props = this.contexts.map(prop => `${prop.key.name}: item${prop.tail}`); const getContext = this.index ? `(item, i) => Object.assign({}, ctx, { ${props.join(', ')}, ${this.index}: i })` diff --git a/src/compile/nodes/Fragment.ts b/src/compile/nodes/Fragment.ts index f0de86fa97fb..e874fe3707ef 100644 --- a/src/compile/nodes/Fragment.ts +++ b/src/compile/nodes/Fragment.ts @@ -23,8 +23,7 @@ export default class Fragment extends Node { name: '@create_main_fragment', key: null, - indexNames: new Map(), - listNames: new Map(), + bindings: new Map(), dependencies: new Set(), }); diff --git a/src/parse/read/context.ts b/src/parse/read/context.ts new file mode 100644 index 000000000000..77fd2cbf6891 --- /dev/null +++ b/src/parse/read/context.ts @@ -0,0 +1,119 @@ +import { Parser } from '../index'; + +type Identifier = { + start: number; + end: number; + type: 'Identifier'; + name: string; +}; + +type Property = { + start: number; + end: number; + type: 'Property'; + key: Identifier; + value: Context; +}; + +type Context = { + start: number; + end: number; + type: 'Identifier' | 'ArrayPattern' | 'ObjectPattern'; + name?: string; + elements?: Context[]; + properties?: Property[]; +} + +function errorOnAssignmentPattern(parser: Parser) { + if (parser.eat('=')) { + parser.error({ + code: 'invalid-assignment-pattern', + message: 'Assignment patterns are not supported' + }, parser.index - 1); + } +} + +export default function readContext(parser: Parser) { + const context: Context = { + start: parser.index, + end: null, + type: null + }; + + if (parser.eat('[')) { + context.type = 'ArrayPattern'; + context.elements = []; + + do { + parser.allowWhitespace(); + + if (parser.template[parser.index] === ',') { + context.elements.push(null); + } else { + context.elements.push(readContext(parser)); + parser.allowWhitespace(); + } + } while (parser.eat(',')); + + errorOnAssignmentPattern(parser); + parser.eat(']', true); + } + + else if (parser.eat('{')) { + context.type = 'ObjectPattern'; + context.properties = []; + + do { + parser.allowWhitespace(); + + const start = parser.index; + const name = parser.readIdentifier(); + const key: Identifier = { + start, + end: parser.index, + type: 'Identifier', + name + }; + parser.allowWhitespace(); + + const value = parser.eat(':') + ? (parser.allowWhitespace(), readContext(parser)) + : key; + + const property: Property = { + start, + end: value.end, + type: 'Property', + key, + value + }; + + context.properties.push(property); + + parser.allowWhitespace(); + } while (parser.eat(',')); + + errorOnAssignmentPattern(parser); + parser.eat('}', true); + } + + else { + const name = parser.readIdentifier(); + if (name) { + context.type = 'Identifier'; + context.end = parser.index; + context.name = name; + } + + else { + parser.error({ + code: 'invalid-context', + message: 'Expected a name, array pattern or object pattern' + }); + } + + errorOnAssignmentPattern(parser); + } + + return context; +} \ No newline at end of file diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts index 1033b6406956..77cbad10584c 100644 --- a/src/parse/state/mustache.ts +++ b/src/parse/state/mustache.ts @@ -1,3 +1,4 @@ +import readContext from '../read/context'; import readExpression from '../read/expression'; import { whitespace } from '../../utils/patterns'; import { trimStart, trimEnd } from '../../utils/trim'; @@ -248,40 +249,7 @@ export default function mustache(parser: Parser) { parser.eat('as', true); parser.requireWhitespace(); - if (parser.eat('[')) { - parser.allowWhitespace(); - - block.destructuredContexts = []; - - do { - parser.allowWhitespace(); - - const destructuredContext = parser.readIdentifier(); - if (!destructuredContext) parser.error({ - code: `expected-name`, - message: `Expected name` - }); - - block.destructuredContexts.push(destructuredContext); - parser.allowWhitespace(); - } while (parser.eat(',')); - - if (!block.destructuredContexts.length) parser.error({ - code: `expected-name`, - message: `Expected name` - }); - - block.context = block.destructuredContexts.join('_'); - - parser.allowWhitespace(); - parser.eat(']', true); - } else { - block.context = parser.readIdentifier(); - if (!block.context) parser.error({ - code: `expected-name`, - message: `Expected name` - }); - } + block.context = readContext(parser); parser.allowWhitespace(); diff --git a/src/utils/unpackDestructuring.ts b/src/utils/unpackDestructuring.ts new file mode 100644 index 000000000000..749d26b34b04 --- /dev/null +++ b/src/utils/unpackDestructuring.ts @@ -0,0 +1,22 @@ +export default function unpackDestructuring( + contexts: Array<{ name: string, tail: string }>, + node: Node, + tail: string +) { + if (!node) return; + + if (node.type === 'Identifier') { + contexts.push({ + key: node, + tail + }); + } else if (node.type === 'ArrayPattern') { + node.elements.forEach((element, i) => { + unpackDestructuring(contexts, element, `${tail}[${i}]`); + }); + } else if (node.type === 'ObjectPattern') { + node.properties.forEach((property) => { + unpackDestructuring(contexts, property.value, `${tail}.${property.key.name}`); + }); + } +} \ No newline at end of file diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts index 8d1502dc3ff7..ed4eaf4d382e 100644 --- a/src/validate/html/index.ts +++ b/src/validate/html/index.ts @@ -8,6 +8,7 @@ import fuzzymatch from '../utils/fuzzymatch' import flattenReference from '../../utils/flattenReference'; import { Validator } from '../index'; import { Node } from '../../interfaces'; +import unpackDestructuring from '../../utils/unpackDestructuring'; function isEmptyBlock(node: Node) { if (!/Block$/.test(node.type) || !node.children) return false; @@ -60,19 +61,17 @@ export default function validateHtml(validator: Validator, html: Node) { } else if (node.type === 'EachBlock') { - if (validator.helpers.has(node.context)) { - let c: number = node.expression.end; - - // find start of context - while (/\s/.test(validator.source[c])) c += 1; - c += 2; - while (/\s/.test(validator.source[c])) c += 1; - - validator.warn({ start: c, end: c + node.context.length }, { - code: `each-context-clash`, - message: `Context clashes with a helper. Rename one or the other to eliminate any ambiguity` - }); - } + const contexts = []; + unpackDestructuring(contexts, node.context, ''); + + contexts.forEach(prop => { + if (validator.helpers.has(prop.key.name)) { + validator.warn(prop.key, { + code: `each-context-clash`, + message: `Context clashes with a helper. Rename one or the other to eliminate any ambiguity` + }); + } + }); } if (validator.options.dev && isEmptyBlock(node)) { diff --git a/test/js/samples/deconflict-builtins/expected-bundle.js b/test/js/samples/deconflict-builtins/expected-bundle.js index 008d28488561..208154d7f329 100644 --- a/test/js/samples/deconflict-builtins/expected-bundle.js +++ b/test/js/samples/deconflict-builtins/expected-bundle.js @@ -250,8 +250,8 @@ function create_each_block(component, ctx) { function get_each_context(ctx, list, i) { return assign(assign({}, ctx), { - each_value: list, node: list[i], + each_value: list, node_index: i }); } diff --git a/test/js/samples/deconflict-builtins/expected.js b/test/js/samples/deconflict-builtins/expected.js index 5ef4396ea06d..a0e71964c48d 100644 --- a/test/js/samples/deconflict-builtins/expected.js +++ b/test/js/samples/deconflict-builtins/expected.js @@ -98,8 +98,8 @@ function create_each_block(component, ctx) { function get_each_context(ctx, list, i) { return assign(assign({}, ctx), { - each_value: list, node: list[i], + each_value: list, node_index: i }); } diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index facaec2b9320..b65f46deb26a 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -297,8 +297,8 @@ function create_each_block(component, ctx) { function get_each_context(ctx, list, i) { return assign(assign({}, ctx), { - each_value: list, comment: list[i], + each_value: list, i: i }); } diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 05af12e2268f..3987454f839e 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -143,8 +143,8 @@ function create_each_block(component, ctx) { function get_each_context(ctx, list, i) { return assign(assign({}, ctx), { - each_value: list, comment: list[i], + each_value: list, i: i }); } diff --git a/test/parser/samples/each-block-destructured/output.json b/test/parser/samples/each-block-destructured/output.json index 38d5ddc770ec..554b0cdcb0f5 100644 --- a/test/parser/samples/each-block-destructured/output.json +++ b/test/parser/samples/each-block-destructured/output.json @@ -1,5 +1,4 @@ { - "hash": "gtdm5e", "html": { "start": 0, "end": 62, @@ -54,11 +53,25 @@ ] } ], - "destructuredContexts": [ - "key", - "value" - ], - "context": "key_value" + "context": { + "start": 18, + "end": null, + "type": "ArrayPattern", + "elements": [ + { + "start": 19, + "end": 22, + "type": "Identifier", + "name": "key" + }, + { + "start": 24, + "end": 29, + "type": "Identifier", + "name": "value" + } + ] + } } ] }, diff --git a/test/parser/samples/each-block-else/output.json b/test/parser/samples/each-block-else/output.json index 9f8c5da79ba8..283b0f0418ad 100644 --- a/test/parser/samples/each-block-else/output.json +++ b/test/parser/samples/each-block-else/output.json @@ -1,5 +1,4 @@ { - "hash": "ljl07n", "html": { "start": 0, "end": 77, @@ -37,7 +36,12 @@ ] } ], - "context": "animal", + "context": { + "start": 18, + "end": 24, + "type": "Identifier", + "name": "animal" + }, "else": { "start": 50, "end": 70, diff --git a/test/parser/samples/each-block-indexed/output.json b/test/parser/samples/each-block-indexed/output.json index 9ffa02aaa871..1039e67b7cb6 100644 --- a/test/parser/samples/each-block-indexed/output.json +++ b/test/parser/samples/each-block-indexed/output.json @@ -1,5 +1,4 @@ { - "hash": "1143n2g", "html": { "start": 0, "end": 58, @@ -54,7 +53,12 @@ ] } ], - "context": "animal", + "context": { + "start": 18, + "end": 24, + "type": "Identifier", + "name": "animal" + }, "index": "i" } ] diff --git a/test/parser/samples/each-block-keyed/output.json b/test/parser/samples/each-block-keyed/output.json index c4cbf98b9e06..e627c5c8c93f 100644 --- a/test/parser/samples/each-block-keyed/output.json +++ b/test/parser/samples/each-block-keyed/output.json @@ -36,7 +36,12 @@ ] } ], - "context": "todo", + "context": { + "start": 16, + "end": 20, + "type": "Identifier", + "name": "todo" + }, "key": { "type": "MemberExpression", "start": 22, diff --git a/test/parser/samples/each-block/output.json b/test/parser/samples/each-block/output.json index 7df4a20eba77..a92f3410d125 100644 --- a/test/parser/samples/each-block/output.json +++ b/test/parser/samples/each-block/output.json @@ -1,5 +1,4 @@ { - "hash": "mzeq0s", "html": { "start": 0, "end": 50, @@ -37,7 +36,12 @@ ] } ], - "context": "animal" + "context": { + "start": 18, + "end": 24, + "type": "Identifier", + "name": "animal" + } } ] }, diff --git a/test/parser/samples/unusual-identifier/output.json b/test/parser/samples/unusual-identifier/output.json index e4a290c0a689..64fbd4902b95 100644 --- a/test/parser/samples/unusual-identifier/output.json +++ b/test/parser/samples/unusual-identifier/output.json @@ -1,5 +1,4 @@ { - "hash": "8weqxs", "html": { "start": 0, "end": 41, @@ -37,7 +36,12 @@ ] } ], - "context": "𐊧" + "context": { + "start": 17, + "end": 19, + "type": "Identifier", + "name": "𐊧" + } } ] }, diff --git a/test/runtime/samples/each-block-destructured-array-sparse/_config.js b/test/runtime/samples/each-block-destructured-array-sparse/_config.js new file mode 100644 index 000000000000..f504cfe3776a --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-sparse/_config.js @@ -0,0 +1,20 @@ +export default { + data: { + animalPawsEntries: [ + ['raccoon', 'hands'], + ['eagle', 'wings'] + ] + }, + + html: ` +

hands

+

wings

+ `, + + test ( assert, component, target ) { + component.set({ animalPawsEntries: [['foo', 'bar']] }); + assert.htmlEqual( target.innerHTML, ` +

bar

+ `); + }, +}; diff --git a/test/runtime/samples/each-block-destructured-array-sparse/main.html b/test/runtime/samples/each-block-destructured-array-sparse/main.html new file mode 100644 index 000000000000..7ca8e403d84f --- /dev/null +++ b/test/runtime/samples/each-block-destructured-array-sparse/main.html @@ -0,0 +1,3 @@ +{#each animalPawsEntries as [, pawType]} +

{pawType}

+{/each} diff --git a/test/runtime/samples/each-block-destructured-object-binding/_config.js b/test/runtime/samples/each-block-destructured-object-binding/_config.js new file mode 100644 index 000000000000..42fba9d8b89d --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-binding/_config.js @@ -0,0 +1,39 @@ +export default { + data: { + people: [{ name: { first: 'Doctor', last: 'Who' } }], + }, + + html: ` + + +

Doctor Who

+ `, + + test(assert, component, target, window) { + const inputs = target.querySelectorAll('input'); + + inputs[1].value = 'Oz'; + inputs[1].dispatchEvent(new window.Event('input')); + + const { people } = component.get(); + + assert.deepEqual(people, [ + { name: { first: 'Doctor', last: 'Oz' } } + ]); + + assert.htmlEqual(target.innerHTML, ` + + +

Doctor Oz

+ `); + + people[0].name.first = 'Frank'; + component.set({ people }); + + assert.htmlEqual(target.innerHTML, ` + + +

Frank Oz

+ `); + }, +}; diff --git a/test/runtime/samples/each-block-destructured-object-binding/main.html b/test/runtime/samples/each-block-destructured-object-binding/main.html new file mode 100644 index 000000000000..049387bc3dbd --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-binding/main.html @@ -0,0 +1,5 @@ +{#each people as { name: { first: f, last: l } } } + + +

{f} {l}

+{/each} diff --git a/test/runtime/samples/each-block-destructured-object/_config.js b/test/runtime/samples/each-block-destructured-object/_config.js new file mode 100644 index 000000000000..f5cb53444793 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object/_config.js @@ -0,0 +1,22 @@ +export default { + data: { + animalPawsEntries: [ + { animal: 'raccoon', pawType: 'hands' }, + { animal: 'eagle', pawType: 'wings' } + ] + }, + + html: ` +

raccoon: hands

+

eagle: wings

+ `, + + test ( assert, component, target ) { + component.set({ + animalPawsEntries: [{ animal: 'cow', pawType: 'hooves' }] + }); + assert.htmlEqual( target.innerHTML, ` +

cow: hooves

+ `); + }, +}; diff --git a/test/runtime/samples/each-block-destructured-object/main.html b/test/runtime/samples/each-block-destructured-object/main.html new file mode 100644 index 000000000000..d759dabf9bd8 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object/main.html @@ -0,0 +1,3 @@ +{#each animalPawsEntries as { animal, pawType } } +

{animal}: {pawType}

+{/each}