diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index 552e30f614..0efa995c81 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -14,6 +14,8 @@ import mapChildren from './shared/mapChildren'; import { dimensions } from '../../utils/patterns'; import fuzzymatch from '../../utils/fuzzymatch'; import list from '../../utils/list'; +import Let from './Let'; +import TemplateScope from './shared/TemplateScope'; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/; @@ -81,11 +83,13 @@ export default class Element extends Node { bindings: Binding[] = []; classes: Class[] = []; handlers: EventHandler[] = []; + lets: Let[] = []; intro?: Transition = null; outro?: Transition = null; animation?: Animation = null; children: Node[]; namespace: string; + scope: TemplateScope; constructor(component, parent, scope, info: any) { super(component, parent, scope, info); @@ -168,6 +172,10 @@ export default class Element extends Node { this.handlers.push(new EventHandler(component, this, scope, node)); break; + case 'Let': + this.lets.push(new Let(component, this, scope, node)); + break; + case 'Transition': const transition = new Transition(component, this, scope, node); if (node.intro) this.intro = transition; @@ -183,7 +191,21 @@ export default class Element extends Node { } }); - this.children = mapChildren(component, this, scope, info.children); + if (this.lets.length > 0) { + this.scope = scope.child(); + + this.lets.forEach(l => { + const dependencies = new Set([l.name]); + + l.names.forEach(name => { + this.scope.add(name, dependencies); + }); + }); + } else { + this.scope = scope; + } + + this.children = mapChildren(component, this, this.scope, info.children); this.validate(); diff --git a/src/compile/nodes/InlineComponent.ts b/src/compile/nodes/InlineComponent.ts index 1aa07bdfc2..8a075d0ce9 100644 --- a/src/compile/nodes/InlineComponent.ts +++ b/src/compile/nodes/InlineComponent.ts @@ -5,15 +5,19 @@ import Binding from './Binding'; import EventHandler from './EventHandler'; import Expression from './shared/Expression'; import Component from '../Component'; +import Let from './Let'; +import TemplateScope from './shared/TemplateScope'; export default class InlineComponent extends Node { type: 'InlineComponent'; name: string; expression: Expression; - attributes: Attribute[]; - bindings: Binding[]; - handlers: EventHandler[]; + attributes: Attribute[] = []; + bindings: Binding[] = []; + handlers: EventHandler[] = []; + lets: Let[] = []; children: Node[]; + scope: TemplateScope; constructor(component: Component, parent, scope, info) { super(component, parent, scope, info); @@ -29,10 +33,6 @@ export default class InlineComponent extends Node { ? new Expression(component, this, scope, info.expression) : null; - this.attributes = []; - this.bindings = []; - this.handlers = []; - info.attributes.forEach(node => { switch (node.type) { case 'Action': @@ -60,6 +60,10 @@ export default class InlineComponent extends Node { this.handlers.push(new EventHandler(component, this, scope, node)); break; + case 'Let': + this.lets.push(new Let(component, this, scope, node)); + break; + case 'Transition': component.error(node, { code: `invalid-transition`, @@ -71,6 +75,20 @@ export default class InlineComponent extends Node { } }); - this.children = mapChildren(component, this, scope, info.children); + if (this.lets.length > 0) { + this.scope = scope.child(); + + this.lets.forEach(l => { + const dependencies = new Set([l.name]); + + l.names.forEach(name => { + this.scope.add(name, dependencies); + }); + }); + } else { + this.scope = scope; + } + + this.children = mapChildren(component, this, this.scope, info.children); } } diff --git a/src/compile/nodes/Let.ts b/src/compile/nodes/Let.ts new file mode 100644 index 0000000000..3d5a7935b8 --- /dev/null +++ b/src/compile/nodes/Let.ts @@ -0,0 +1,27 @@ +import Node from './shared/Node'; +import Expression from './shared/Expression'; +import Component from '../Component'; + +class Pattern { + constructor(node) { + // TODO implement `let:foo={bar}` and `let:contact={{ name, address }}` etc + } +} + +export default class Let extends Node { + type: 'Let'; + name: string; + pattern: Pattern; + names: string[]; + + constructor(component: Component, parent, scope, info) { + super(component, parent, scope, info); + + this.name = info.name; + + this.pattern = info.expression && new Pattern(info.expression); + + // TODO + this.names = [this.name]; + } +} \ No newline at end of file diff --git a/src/compile/nodes/Slot.ts b/src/compile/nodes/Slot.ts index 028f42852b..d66ec8c6c0 100644 --- a/src/compile/nodes/Slot.ts +++ b/src/compile/nodes/Slot.ts @@ -19,26 +19,28 @@ export default class Slot extends Element { }); } - if (attr.name !== 'name') { - component.error(attr, { - code: `invalid-slot-attribute`, - message: `"name" is the only attribute permitted on elements` - }); - } + // if (attr.name !== 'name') { + // component.error(attr, { + // code: `invalid-slot-attribute`, + // message: `"name" is the only attribute permitted on elements` + // }); + // } - if (attr.value.length !== 1 || attr.value[0].type !== 'Text') { - component.error(attr, { - code: `dynamic-slot-name`, - message: ` name cannot be dynamic` - }); - } + if (attr.name === 'name') { + if (attr.value.length !== 1 || attr.value[0].type !== 'Text') { + component.error(attr, { + code: `dynamic-slot-name`, + message: ` name cannot be dynamic` + }); + } - const slotName = attr.value[0].data; - if (slotName === 'default') { - component.error(attr, { - code: `invalid-slot-name`, - message: `default is a reserved word — it cannot be used as a slot name` - }); + const slotName = attr.value[0].data; + if (slotName === 'default') { + component.error(attr, { + code: `invalid-slot-name`, + message: `default is a reserved word — it cannot be used as a slot name` + }); + } } // TODO should duplicate slots be disallowed? Feels like it's more likely to be a diff --git a/src/compile/render-dom/wrappers/Element/index.ts b/src/compile/render-dom/wrappers/Element/index.ts index 2bd4d7f13d..0345026fe6 100644 --- a/src/compile/render-dom/wrappers/Element/index.ts +++ b/src/compile/render-dom/wrappers/Element/index.ts @@ -130,14 +130,22 @@ export default class ElementWrapper extends Wrapper { if (owner && owner.node.type === 'InlineComponent') { const name = attribute.getStaticValue(); - this.slot_block = block.child({ - comment: createDebuggingComment(node, this.renderer.component), - name: this.renderer.component.getUniqueName(`create_${sanitize(name)}_slot`) - }); + if (!(owner).slots.has(name)) { + const child_block = block.child({ + comment: createDebuggingComment(node, this.renderer.component), + name: this.renderer.component.getUniqueName(`create_${sanitize(name)}_slot`) + }); + + const fn = `({ thing }) => ({ thing })`; - (owner).slots.set(name, this.slot_block); - this.renderer.blocks.push(this.slot_block); + (owner).slots.set(name, { + block: child_block, + fn + }); + this.renderer.blocks.push(child_block); + } + this.slot_block = (owner).slots.get(name).block; block = this.slot_block; } } diff --git a/src/compile/render-dom/wrappers/InlineComponent/index.ts b/src/compile/render-dom/wrappers/InlineComponent/index.ts index 77b00fb9a8..643dba89fb 100644 --- a/src/compile/render-dom/wrappers/InlineComponent/index.ts +++ b/src/compile/render-dom/wrappers/InlineComponent/index.ts @@ -13,10 +13,11 @@ import getObject from '../../../../utils/getObject'; import flattenReference from '../../../../utils/flattenReference'; import createDebuggingComment from '../../../../utils/createDebuggingComment'; import sanitize from '../../../../utils/sanitize'; +import { get_context_merger } from '../shared/get_context_merger'; export default class InlineComponentWrapper extends Wrapper { var: string; - slots: Map = new Map(); + slots: Map = new Map(); node: InlineComponent; fragment: FragmentWrapper; @@ -71,7 +72,13 @@ export default class InlineComponentWrapper extends Wrapper { name: renderer.component.getUniqueName(`create_default_slot`) }); this.renderer.blocks.push(default_slot); - this.slots.set('default', default_slot); + + const fn = get_context_merger(this.node.lets); + + this.slots.set('default', { + block: default_slot, + fn + }); this.fragment = new FragmentWrapper(renderer, default_slot, node.children, this, stripWhitespace, nextSibling); block.addDependencies(default_slot.dependencies); @@ -101,7 +108,7 @@ export default class InlineComponentWrapper extends Wrapper { const usesSpread = !!this.node.attributes.find(a => a.isSpread); - const slot_props = Array.from(this.slots).map(([name, block]) => `$$slot_${sanitize(name)}: ${block.name}`); + const slot_props = Array.from(this.slots).map(([name, slot]) => `$$slot_${sanitize(name)}: [${slot.block.name}${slot.fn ? `, ${slot.fn}` : ''}]`); if (slot_props.length > 0) slot_props.push(`$$scope: { ctx }`); const attributeObject = usesSpread @@ -123,7 +130,7 @@ export default class InlineComponentWrapper extends Wrapper { const default_slot = this.slots.get('default'); this.fragment.nodes.forEach((child: Wrapper) => { - child.render(default_slot, null, 'nodes'); + child.render(default_slot.block, null, 'nodes'); }); } @@ -136,8 +143,8 @@ export default class InlineComponentWrapper extends Wrapper { } const fragment_dependencies = new Set(); - this.slots.forEach(block => { - block.dependencies.forEach(name => { + this.slots.forEach(slot => { + slot.block.dependencies.forEach(name => { if (renderer.component.mutable_props.has(name)) { fragment_dependencies.add(name); } diff --git a/src/compile/render-dom/wrappers/Slot.ts b/src/compile/render-dom/wrappers/Slot.ts index 81262fb6d3..e8be2caead 100644 --- a/src/compile/render-dom/wrappers/Slot.ts +++ b/src/compile/render-dom/wrappers/Slot.ts @@ -33,7 +33,7 @@ export default class SlotWrapper extends Wrapper { nextSibling ); - block.addDependencies(new Set('$$scope')); + block.addDependencies(new Set(['$$scope'])); } render( @@ -49,7 +49,7 @@ export default class SlotWrapper extends Wrapper { const slot = block.getUniqueName(`${sanitize(slot_name)}_slot`); block.builders.init.addLine( - `const ${slot} = ctx.$$slot_${sanitize(slot_name)} && ctx.$$slot_${sanitize(slot_name)}(ctx.$$scope.ctx);` + `const ${slot} = @create_slot(ctx.$$slot_${sanitize(slot_name)}, ctx);` ); let mountBefore = block.builders.mount.toString(); diff --git a/src/compile/render-dom/wrappers/shared/get_context_merger.ts b/src/compile/render-dom/wrappers/shared/get_context_merger.ts new file mode 100644 index 0000000000..c02cb8cca3 --- /dev/null +++ b/src/compile/render-dom/wrappers/shared/get_context_merger.ts @@ -0,0 +1,7 @@ +import Let from '../../../nodes/Let'; + +export function get_context_merger(lets: Let[]) { + if (lets.length === 0) return null; + + return `({ ${lets.map(l => l.name).join(', ')} }) => ({ ${lets.map(l => l.name).join(', ')} })`; +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/Element.ts b/src/compile/render-ssr/handlers/Element.ts index 3a4316abfa..49c3132e31 100644 --- a/src/compile/render-ssr/handlers/Element.ts +++ b/src/compile/render-ssr/handlers/Element.ts @@ -2,8 +2,9 @@ import { quotePropIfNecessary, quoteNameIfNecessary } from '../../../utils/quote import isVoidElementName from '../../../utils/isVoidElementName'; import Attribute from '../../nodes/Attribute'; import Node from '../../nodes/shared/Node'; -import { escape, escapeTemplate } from '../../../utils/stringify'; import { snip } from '../utils'; +import { stringify_attribute } from './shared/stringify_attribute'; +import { get_slot_context } from './shared/get_slot_context'; // source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7 const boolean_attributes = new Set([ @@ -57,6 +58,8 @@ export default function(node, renderer, options) { const target = renderer.targets[renderer.targets.length - 1]; target.slotStack.push(slotName); target.slots[slotName] = ''; + + options.slot_contexts.set(slotName, get_slot_context(node.lets)); } const classExpr = node.classes.map((classDir: Class) => { @@ -75,7 +78,7 @@ export default function(node, renderer, options) { args.push(snip(attribute.expression)); } else { if (attribute.name === 'value' && node.name === 'textarea') { - textareaContents = stringifyAttribute(attribute); + textareaContents = stringify_attribute(attribute); } else if (attribute.isTrue) { args.push(`{ ${quoteNameIfNecessary(attribute.name)}: true }`); } else if ( @@ -86,7 +89,7 @@ export default function(node, renderer, options) { // a boolean attribute with one non-Text chunk args.push(`{ ${quoteNameIfNecessary(attribute.name)}: ${snip(attribute.chunks[0])} }`); } else { - args.push(`{ ${quoteNameIfNecessary(attribute.name)}: \`${stringifyAttribute(attribute)}\` }`); + args.push(`{ ${quoteNameIfNecessary(attribute.name)}: \`${stringify_attribute(attribute)}\` }`); } } }); @@ -97,7 +100,7 @@ export default function(node, renderer, options) { if (attribute.type !== 'Attribute') return; if (attribute.name === 'value' && node.name === 'textarea') { - textareaContents = stringifyAttribute(attribute); + textareaContents = stringify_attribute(attribute); } else if (attribute.isTrue) { openingTag += ` ${attribute.name}`; } else if ( @@ -109,14 +112,14 @@ export default function(node, renderer, options) { openingTag += '${' + snip(attribute.chunks[0]) + ' ? " ' + attribute.name + '" : "" }'; } else if (attribute.name === 'class' && classExpr) { addClassAttribute = false; - openingTag += ` class="\${[\`${stringifyAttribute(attribute)}\`, ${classExpr}].join(' ').trim() }"`; + openingTag += ` class="\${[\`${stringify_attribute(attribute)}\`, ${classExpr}].join(' ').trim() }"`; } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { const { name } = attribute; const snippet = snip(attribute.chunks[0]); openingTag += '${(v => v == null ? "" : ` ' + name + '="${@escape(' + snippet + ')}"`)(' + snippet + ')}'; } else { - openingTag += ` ${attribute.name}="${stringifyAttribute(attribute)}"`; + openingTag += ` ${attribute.name}="${stringify_attribute(attribute)}"`; } }); } @@ -149,16 +152,4 @@ export default function(node, renderer, options) { if (!isVoidElementName(node.name)) { renderer.append(``); } -} - -function stringifyAttribute(attribute: Attribute) { - return attribute.chunks - .map((chunk: Node) => { - if (chunk.type === 'Text') { - return escapeTemplate(escape(chunk.data).replace(/"/g, '"')); - } - - return '${@escape(' + snip(chunk) + ')}'; - }) - .join(''); } \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/InlineComponent.ts b/src/compile/render-ssr/handlers/InlineComponent.ts index 408286b6ac..83e08a1511 100644 --- a/src/compile/render-ssr/handlers/InlineComponent.ts +++ b/src/compile/render-ssr/handlers/InlineComponent.ts @@ -1,11 +1,9 @@ import { escape, escapeTemplate, stringify } from '../../../utils/stringify'; -import getObject from '../../../utils/getObject'; -import { get_tail_snippet } from '../../../utils/get_tail_snippet'; -import { quoteNameIfNecessary, quotePropIfNecessary } from '../../../utils/quoteIfNecessary'; -import deindent from '../../../utils/deindent'; +import { quoteNameIfNecessary } from '../../../utils/quoteIfNecessary'; import { snip } from '../utils'; import Renderer from '../Renderer'; import stringifyProps from '../../../utils/stringifyProps'; +import { get_slot_context } from './shared/get_slot_context'; type AppendTarget = any; // TODO @@ -33,12 +31,6 @@ function getAttributeValue(attribute) { return '`' + attribute.chunks.map(stringifyAttribute).join('') + '`'; } -function stringifyObject(props) { - return props.length > 0 - ? `{ ${props.join(', ')} }` - : `{};` -} - export default function(node, renderer: Renderer, options) { const binding_props = []; const binding_fns = []; @@ -98,11 +90,18 @@ export default function(node, renderer: Renderer, options) { renderer.targets.push(target); - renderer.render(node.children, options); + const slot_contexts = new Map(); + slot_contexts.set('default', get_slot_context(node.lets)); + + renderer.render(node.children, Object.assign({}, options, { + slot_contexts + })); Object.keys(target.slots).forEach(name => { + const slot_context = slot_contexts.get(name); + slot_fns.push( - `${quoteNameIfNecessary(name)}: () => \`${target.slots[name]}\`` + `${quoteNameIfNecessary(name)}: (${slot_context}) => \`${target.slots[name]}\`` ); }); diff --git a/src/compile/render-ssr/handlers/Slot.ts b/src/compile/render-ssr/handlers/Slot.ts index 704f17fdc1..04fca84538 100644 --- a/src/compile/render-ssr/handlers/Slot.ts +++ b/src/compile/render-ssr/handlers/Slot.ts @@ -1,4 +1,6 @@ import { quotePropIfNecessary } from '../../../utils/quoteIfNecessary'; +import { snip } from '../utils'; +import { stringify_attribute } from './shared/stringify_attribute'; export default function(node, renderer, options) { const name = node.attributes.find(attribute => attribute.name === 'name'); @@ -6,7 +8,23 @@ export default function(node, renderer, options) { const slot_name = name && name.chunks[0].data || 'default'; const prop = quotePropIfNecessary(slot_name); - renderer.append(`\${$$slots${prop} ? $$slots${prop}() : \``); + const slot_data = node.attributes + .filter(attribute => attribute.name !== 'name') + .map(attribute => { + const value = attribute.isTrue + ? 'true' + : attribute.chunks.length === 0 + ? '""' + : attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text' + ? snip(attribute.chunks[0]) + : stringify_attribute(attribute); + + return `${attribute.name}: ${value}`; + }); + + const arg = slot_data ? `{ ${slot_data.join(', ')} }` : ''; + + renderer.append(`\${$$slots${prop} ? $$slots${prop}(${arg}) : \``); renderer.render(node.children, options); diff --git a/src/compile/render-ssr/handlers/shared/get_slot_context.ts b/src/compile/render-ssr/handlers/shared/get_slot_context.ts new file mode 100644 index 0000000000..5f4e4cfe69 --- /dev/null +++ b/src/compile/render-ssr/handlers/shared/get_slot_context.ts @@ -0,0 +1,6 @@ +import Let from '../../../nodes/Let'; + +export function get_slot_context(lets: Let[]) { + if (lets.length === 0) return ''; + return `{ ${lets.map(l => `${l.name}: ${l.name}`)} }`; // TODO support aliased/destructured lets +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/shared/stringify_attribute.ts b/src/compile/render-ssr/handlers/shared/stringify_attribute.ts new file mode 100644 index 0000000000..1182d61190 --- /dev/null +++ b/src/compile/render-ssr/handlers/shared/stringify_attribute.ts @@ -0,0 +1,16 @@ +import Attribute from '../../../nodes/Attribute'; +import Node from '../../../nodes/shared/Node'; +import { escapeTemplate, escape } from '../../../../utils/stringify'; +import { snip } from '../../utils'; + +export function stringify_attribute(attribute: Attribute) { + return attribute.chunks + .map((chunk: Node) => { + if (chunk.type === 'Text') { + return escapeTemplate(escape(chunk.data).replace(/"/g, '"')); + } + + return '${@escape(' + snip(chunk) + ')}'; + }) + .join(''); +} \ No newline at end of file diff --git a/src/internal/utils.js b/src/internal/utils.js index a46385190d..1ae4fbdb4f 100644 --- a/src/internal/utils.js +++ b/src/internal/utils.js @@ -56,4 +56,14 @@ export function validate_store(store, name) { if (!store || typeof store.subscribe !== 'function') { throw new Error(`'${name}' is not a store with a 'subscribe' method`); } +} + +export function create_slot(definition, ctx) { + if (definition) { + const slot_ctx = definition[1] + ? assign({}, assign(ctx.$$scope.ctx, definition[1](ctx))) + : ctx.$$scope.ctx; + + return definition[0](slot_ctx); + } } \ No newline at end of file diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index 6e53ab03e0..c11c660082 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -182,18 +182,19 @@ export default function tag(parser: Parser) { } } - if (name === 'slot') { - let i = parser.stack.length; - while (i--) { - const item = parser.stack[i]; - if (item.type === 'EachBlock') { - parser.error({ - code: `invalid-slot-placement`, - message: ` cannot be a child of an each-block` - }, start); - } - } - } + // TODO should this still error in in web component mode? + // if (name === 'slot') { + // let i = parser.stack.length; + // while (i--) { + // const item = parser.stack[i]; + // if (item.type === 'EachBlock') { + // parser.error({ + // code: `invalid-slot-placement`, + // message: ` cannot be a child of an each-block` + // }, start); + // } + // } + // } const uniqueNames = new Set(); @@ -464,6 +465,7 @@ function get_directive_type(name) { if (name === 'bind') return 'Binding'; if (name === 'class') return 'Class'; if (name === 'on') return 'EventHandler'; + if (name === 'let') return 'Let'; if (name === 'ref') return 'Ref'; if (name === 'in' || name === 'out' || name === 'transition') return 'Transition'; } diff --git a/test/runtime/samples/component-slot-let-named/Nested.html b/test/runtime/samples/component-slot-let-named/Nested.html new file mode 100644 index 0000000000..d95593fc18 --- /dev/null +++ b/test/runtime/samples/component-slot-let-named/Nested.html @@ -0,0 +1,5 @@ +
+ {#each things as thing} + + {/each} +
\ No newline at end of file diff --git a/test/runtime/samples/component-slot-let-named/_config.js b/test/runtime/samples/component-slot-let-named/_config.js new file mode 100644 index 0000000000..f65448af93 --- /dev/null +++ b/test/runtime/samples/component-slot-let-named/_config.js @@ -0,0 +1,24 @@ +export default { + props: { + things: [1, 2, 3] + }, + + html: ` +
+
1
+
2
+
3
+
`, + + test({ assert, component, target }) { + component.things = [1, 2, 3, 4]; + assert.htmlEqual(target.innerHTML, ` +
+
1
+
2
+
3
+
4
+
+ `); + } +}; diff --git a/test/runtime/samples/component-slot-let-named/main.html b/test/runtime/samples/component-slot-let-named/main.html new file mode 100644 index 0000000000..1c3d3365c7 --- /dev/null +++ b/test/runtime/samples/component-slot-let-named/main.html @@ -0,0 +1,11 @@ + + + +
+ {thing} +
+
\ No newline at end of file diff --git a/test/runtime/samples/component-slot-let/Nested.html b/test/runtime/samples/component-slot-let/Nested.html new file mode 100644 index 0000000000..38c6ed6cbe --- /dev/null +++ b/test/runtime/samples/component-slot-let/Nested.html @@ -0,0 +1,5 @@ +
+ {#each things as thing} + + {/each} +
\ No newline at end of file diff --git a/test/runtime/samples/component-slot-let/_config.js b/test/runtime/samples/component-slot-let/_config.js new file mode 100644 index 0000000000..d66f613bb4 --- /dev/null +++ b/test/runtime/samples/component-slot-let/_config.js @@ -0,0 +1,24 @@ +export default { + props: { + things: [1, 2, 3] + }, + + html: ` +
+ 1 + 2 + 3 +
`, + + test({ assert, component, target }) { + component.things = [1, 2, 3, 4]; + assert.htmlEqual(target.innerHTML, ` +
+ 1 + 2 + 3 + 4 +
+ `); + } +}; diff --git a/test/runtime/samples/component-slot-let/main.html b/test/runtime/samples/component-slot-let/main.html new file mode 100644 index 0000000000..76d491389f --- /dev/null +++ b/test/runtime/samples/component-slot-let/main.html @@ -0,0 +1,9 @@ + + + + {thing} + \ No newline at end of file