diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 323af454c7fa..65574ee30372 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -763,6 +763,10 @@ export default class Generator { node.metadata = contextualise(node.expression, contextDependencies, indexes); this.skip(); } + + if (node.type === 'Element' && node.name === ':Component') { + node.metadata = contextualise(node.expression, contextDependencies, indexes); + } }, leave(node: Node, parent: Node) { diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 62768184a065..89af90a36eac 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -256,6 +256,7 @@ const preprocessors = { ) => { cannotUseInnerHTML(node); node.var = block.getUniqueName(`each`); + node.iterations = block.getUniqueName(`${node.var}_blocks`); const { dependencies } = node.metadata; block.addDependencies(dependencies); @@ -436,13 +437,17 @@ const preprocessors = { } const isComponent = - generator.components.has(node.name) || node.name === ':Self'; + generator.components.has(node.name) || node.name === ':Self' || node.name === ':Component'; if (isComponent) { cannotUseInnerHTML(node); node.var = block.getUniqueName( - (node.name === ':Self' ? generator.name : node.name).toLowerCase() + ( + node.name === ':Self' ? generator.name : + node.name === ':Component' ? 'switch_instance' : + node.name + ).toLowerCase() ); node._state = getChildState(state, { diff --git a/src/generators/dom/visitors/Component.ts b/src/generators/dom/visitors/Component.ts index bfd52559d679..f3487b5a5c8c 100644 --- a/src/generators/dom/visitors/Component.ts +++ b/src/generators/dom/visitors/Component.ts @@ -3,9 +3,11 @@ import CodeBuilder from '../../../utils/CodeBuilder'; import visit from '../visit'; import { DomGenerator } from '../index'; import Block from '../Block'; +import isDomNode from './shared/isDomNode'; import getTailSnippet from '../../../utils/getTailSnippet'; import getObject from '../../../utils/getObject'; import getExpressionPrecedence from '../../../utils/getExpressionPrecedence'; +import getStaticAttributeValue from '../../../utils/getStaticAttributeValue'; import { stringify } from '../../../utils/stringify'; import stringifyProps from '../../../utils/stringifyProps'; import { Node } from '../../../interfaces'; @@ -60,12 +62,21 @@ export default function visitComponent( let beforecreate: string = null; const attributes = node.attributes - .filter((a: Node) => a.type === 'Attribute') - .map((a: Node) => mungeAttribute(a, block)); + .filter(a => a.type === 'Attribute') + .map(a => mungeAttribute(a, block)); const bindings = node.attributes - .filter((a: Node) => a.type === 'Binding') - .map((a: Node) => mungeBinding(a, block)); + .filter(a => a.type === 'Binding') + .map(a => mungeBinding(a, block)); + + const eventHandlers = node.attributes + .filter((a: Node) => a.type === 'EventHandler') + .map(a => mungeEventHandler(generator, node, a, block, name_context, allContexts)); + + const ref = node.attributes.find((a: Node) => a.type === 'Ref'); + if (ref) generator.usesRefs = true; + + const updates: string[] = []; if (attributes.length || bindings.length) { const initialProps = attributes @@ -73,8 +84,6 @@ export default function visitComponent( const initialPropString = stringifyProps(initialProps); - const updates: string[] = []; - attributes .filter((attribute: Attribute) => attribute.dynamic) .forEach((attribute: Attribute) => { @@ -194,96 +203,161 @@ export default function visitComponent( } else if (initialProps.length) { componentInitProperties.push(`data: ${initialPropString}`); } + } - if (updates.length) { - block.builders.update.addBlock(deindent` - var ${name}_changes = {}; - ${updates.join('\n')} - ${name}._set(${name}_changes); - ${bindings.length && `${name_updating} = {};`} - `); + const isDynamicComponent = node.name === ':Component'; + + const switch_vars = isDynamicComponent && { + value: block.getUniqueName('switch_value'), + props: block.getUniqueName('switch_props') + }; + + const expression = ( + node.name === ':Self' ? generator.name : + isDynamicComponent ? switch_vars.value : + `%components-${node.name}` + ); + + if (isDynamicComponent) { + block.contextualise(node.expression); + const { dependencies, snippet } = node.metadata; + + const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode || !isDomNode(node.parent, generator); + const anchor = needsAnchor + ? block.getUniqueName(`${name}_anchor`) + : (node.next && node.next.var) || 'null'; + + if (needsAnchor) { + block.addElement( + anchor, + `@createComment()`, + `@createComment()`, + state.parentNode + ); } - } - const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`; + const params = block.params.join(', '); - block.builders.init.addBlock(deindent` - ${statements.join('\n')} - var ${name} = new ${expression}({ - ${componentInitProperties.join(',\n')} - }); + block.builders.init.addBlock(deindent` + var ${switch_vars.value} = ${snippet}; - ${beforecreate} - `); + function ${switch_vars.props}(${params}) { + return { + ${componentInitProperties.join(',\n')} + }; + } - block.builders.create.addLine(`${name}._fragment.c();`); + if (${switch_vars.value}) { + ${statements.length > 0 && statements.join('\n')} + var ${name} = new ${expression}(${switch_vars.props}(${params})); - block.builders.claim.addLine( - `${name}._fragment.l(${state.parentNodes});` - ); + ${beforecreate} + } - block.builders.mount.addLine( - `${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});` - ); + ${eventHandlers.map(handler => deindent` + function ${handler.var}(event) { + ${handler.body} + } - if (!state.parentNode) block.builders.unmount.addLine(`${name}._unmount();`); + if (${name}) ${name}.on("${handler.name}", ${handler.var}); + `)} + `); - block.builders.destroy.addLine(`${name}.destroy(false);`); + block.builders.create.addLine( + `if (${name}) ${name}._fragment.c();` + ); - // event handlers - node.attributes.filter((a: Node) => a.type === 'EventHandler').forEach((handler: Node) => { - const usedContexts: string[] = []; + block.builders.claim.addLine( + `if (${name}) ${name}._fragment.l(${state.parentNodes});` + ); - if (handler.expression) { - generator.addSourcemapLocations(handler.expression); - generator.code.prependRight( - handler.expression.start, - `${block.alias('component')}.` - ); + block.builders.mount.addLine( + `if (${name}) ${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});` + ); - handler.expression.arguments.forEach((arg: Node) => { - const { contexts } = block.contextualise(arg, null, true); + block.builders.update.addBlock(deindent` + if (${switch_vars.value} !== (${switch_vars.value} = ${snippet})) { + if (${name}) ${name}.destroy(); - contexts.forEach(context => { - if (!~usedContexts.indexOf(context)) usedContexts.push(context); - allContexts.add(context); - }); - }); - } + if (${switch_vars.value}) { + ${name} = new ${switch_vars.value}(${switch_vars.props}(${params})); + ${name}._fragment.c(); - // TODO hoist event handlers? can do `this.__component.method(...)` - const declarations = usedContexts.map(name => { - if (name === 'state') return `var state = ${name_context}.state;`; + ${node.children.map(child => remount(generator, child, name))} + ${name}._mount(${anchor}.parentNode, ${anchor}); - const listName = block.listNames.get(name); - const indexName = block.indexNames.get(name); + ${eventHandlers.map(handler => deindent` + ${name}.on("${handler.name}", ${handler.var}); + `)} - return `var ${listName} = ${name_context}.${listName}, ${indexName} = ${name_context}.${indexName}, ${name} = ${listName}[${indexName}]`; - }); + ${ref && `#component.refs.${ref.name} = ${name};`} + } - const handlerBody = - (declarations.length ? declarations.join('\n') + '\n\n' : '') + - (handler.expression ? - `[✂${handler.expression.start}-${handler.expression.end}✂];` : - `${block.alias('component')}.fire('${handler.name}', event);`); + ${ref && deindent` + else if (#component.refs.${ref.name} === ${name}) { + #component.refs.${ref.name} = null; + }`} + } + `); + if (updates.length) { + block.builders.update.addBlock(deindent` + else { + var ${name}_changes = {}; + ${updates.join('\n')} + ${name}._set(${name}_changes); + ${bindings.length && `${name_updating} = {};`} + } + `); + } + + if (!state.parentNode) block.builders.unmount.addLine(`if (${name}) ${name}._unmount();`); + + block.builders.destroy.addLine(`if (${name}) ${name}.destroy(false);`); + } else { block.builders.init.addBlock(deindent` - ${name}.on("${handler.name}", function(event) { - ${handlerBody} + ${statements.join('\n')} + var ${name} = new ${expression}({ + ${componentInitProperties.join(',\n')} }); + + ${beforecreate} + + ${eventHandlers.map(handler => deindent` + ${name}.on("${handler.name}", function(event) { + ${handler.body} + }); + `)} + + ${ref && `#component.refs.${ref.name} = ${name};`} `); - }); - // refs - node.attributes.filter((a: Node) => a.type === 'Ref').forEach((ref: Node) => { - generator.usesRefs = true; + block.builders.create.addLine(`${name}._fragment.c();`); + + block.builders.claim.addLine( + `${name}._fragment.l(${state.parentNodes});` + ); - block.builders.init.addLine(`#component.refs.${ref.name} = ${name};`); + block.builders.mount.addLine( + `${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});` + ); + + if (updates.length) { + block.builders.update.addBlock(deindent` + var ${name}_changes = {}; + ${updates.join('\n')} + ${name}._set(${name}_changes); + ${bindings.length && `${name_updating} = {};`} + `); + } + + if (!state.parentNode) block.builders.unmount.addLine(`${name}._unmount();`); block.builders.destroy.addLine(deindent` - if (#component.refs.${ref.name} === ${name}) #component.refs.${ref.name} = null; + ${name}.destroy(false); + ${ref && `if (#component.refs.${ref.name} === ${name}) #component.refs.${ref.name} = null;`} `); - }); + } // maintain component context if (allContexts.size) { @@ -427,6 +501,55 @@ function mungeBinding(binding: Node, block: Block): Binding { }; } +function mungeEventHandler(generator: DomGenerator, node: Node, handler: Node, block: Block, name_context: string, allContexts: Set) { + let body; + + if (handler.expression) { + generator.addSourcemapLocations(handler.expression); + generator.code.prependRight( + handler.expression.start, + `${block.alias('component')}.` + ); + + const usedContexts: string[] = []; + + handler.expression.arguments.forEach((arg: Node) => { + const { contexts } = block.contextualise(arg, null, true); + + contexts.forEach(context => { + if (!~usedContexts.indexOf(context)) usedContexts.push(context); + allContexts.add(context); + }); + }); + + // TODO hoist event handlers? can do `this.__component.method(...)` + const declarations = usedContexts.map(name => { + if (name === 'state') return `var state = ${name_context}.state;`; + + const listName = block.listNames.get(name); + const indexName = block.indexNames.get(name); + + return `var ${listName} = ${name_context}.${listName}, ${indexName} = ${name_context}.${indexName}, ${name} = ${listName}[${indexName}]`; + }); + + body = deindent` + ${declarations} + + [✂${handler.expression.start}-${handler.expression.end}✂]; + `; + } else { + body = deindent` + ${block.alias('component')}.fire('${handler.name}', event); + `; + } + + return { + name: handler.name, + var: block.getUniqueName(`${node.var}_${handler.name}`), + body + }; +} + function isComputed(node: Node) { while (node.type === 'MemberExpression') { if (node.computed) return true; @@ -434,4 +557,32 @@ function isComputed(node: Node) { } return false; +} + +function remount(generator: DomGenerator, node: Node, name: string) { + // TODO make this a method of the nodes + + if (node.type === 'Element') { + if (node.name === ':Self' || node.name === ':Component' || generator.components.has(node.name)) { + return `${node.var}._mount(${name}._slotted.default, null);`; + } + + const slot = node.attributes.find(attribute => attribute.name === 'slot'); + if (slot) { + return `@appendNode(${node.var}, ${name}._slotted.${getStaticAttributeValue(node, 'slot')});`; + } + + return `@appendNode(${node.var}, ${name}._slotted.default);`; + } + + if (node.type === 'Text' || node.type === 'MustacheTag' || node.type === 'RawMustacheTag') { + return `@appendNode(${node.var}, ${name}._slotted.default);`; + } + + if (node.type === 'EachBlock') { + // TODO consider keyed blocks + return `for (var #i = 0; #i < ${node.iterations}.length; #i += 1) ${node.iterations}[#i].m(${name}._slotted.default, null);`; + } + + return `${node.var}.m(${name}._slotted.default, null);`; } \ No newline at end of file diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index fd36e6533d3a..6b659ccf068e 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -18,7 +18,7 @@ export default function visitEachBlock( const create_each_block = node._block.name; const each_block_value = node._block.listName; - const iterations = block.getUniqueName(`${each}_blocks`); + const iterations = node.iterations; const params = block.params.join(', '); const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode || !isDomNode(node.parent, generator); diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 8de38c766d01..3121e24a013e 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -43,7 +43,7 @@ export default function visitElement( } } - if (generator.components.has(node.name) || node.name === ':Self') { + if (generator.components.has(node.name) || node.name === ':Self' || node.name === ':Component') { return visitComponent(generator, block, state, node, elementStack, componentStack); } diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index f9c40ddb085d..13ad9f026dc6 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -189,6 +189,14 @@ export default function ssr( } ` } + + ${ + /__missingComponent/.test(generator.renderCode) && deindent` + var __missingComponent = { + render: () => '' + }; + ` + } `.replace(/(@+|#+|%+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => { if (sigil === '@') return generator.alias(name); if (sigil === '%') return generator.templateVars.get(name); diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts index 74c7e239e458..d6f44abb23c7 100644 --- a/src/generators/server-side-rendering/visitors/Component.ts +++ b/src/generators/server-side-rendering/visitors/Component.ts @@ -71,7 +71,14 @@ export default function visitComponent( ) .join(', '); - const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`; + const isDynamicComponent = node.name === ':Component'; + if (isDynamicComponent) block.contextualise(node.expression); + + const expression = ( + node.name === ':Self' ? generator.name : + isDynamicComponent ? `((${node.metadata.snippet}) || __missingComponent)` : + `%components-${node.name}` + ); bindings.forEach(binding => { block.addBinding(binding, expression); diff --git a/src/generators/server-side-rendering/visitors/Element.ts b/src/generators/server-side-rendering/visitors/Element.ts index e1d7e197d040..25da587a1f9f 100644 --- a/src/generators/server-side-rendering/visitors/Element.ts +++ b/src/generators/server-side-rendering/visitors/Element.ts @@ -40,7 +40,7 @@ export default function visitElement( return; } - if (generator.components.has(node.name) || node.name === ':Self') { + if (generator.components.has(node.name) || node.name === ':Self' || node.name === ':Component') { visitComponent(generator, block, node); return; } diff --git a/src/generators/shared/utils/isChildOfComponent.ts b/src/generators/shared/utils/isChildOfComponent.ts index 2de64c486014..77275f3d8cb6 100644 --- a/src/generators/shared/utils/isChildOfComponent.ts +++ b/src/generators/shared/utils/isChildOfComponent.ts @@ -4,7 +4,7 @@ import Generator from '../../Generator'; export default function isChildOfComponent(node: Node, generator: Generator) { while (node = node.parent) { if (node.type !== 'Element') continue; - if (generator.components.has(node.name)) return true; + if (node.name === ':Self' || node.name === ':Component' || generator.components.has(node.name)) return true; // TODO extract this out into a helper if (/-/.test(node.name)) return false; } } \ No newline at end of file diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index ffe9a4021866..90d9f5cd2c67 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -15,9 +15,10 @@ import { Node } from '../../interfaces'; const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const SELF = ':Self'; +const COMPONENT = ':Component'; const metaTags = { - ':Window': true, + ':Window': true }; const specials = new Map([ @@ -104,6 +105,15 @@ export default function tag(parser: Parser) { } } + const element: Node = { + start, + end: null, // filled in later + type: 'Element', + name, + attributes: [], + children: [], + }; + parser.allowWhitespace(); if (isClosingTag) { @@ -156,17 +166,22 @@ export default function tag(parser: Parser) { } } - const attributes = []; + if (name === COMPONENT) { + parser.eat('{', true); + element.expression = readExpression(parser); + parser.allowWhitespace(); + parser.eat('}', true); + parser.allowWhitespace(); + } + const uniqueNames = new Set(); let attribute; while ((attribute = readAttribute(parser, uniqueNames))) { - attributes.push(attribute); + element.attributes.push(attribute); parser.allowWhitespace(); } - parser.allowWhitespace(); - // special cases – top-level \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-events/Bar.html b/test/runtime/samples/dynamic-component-events/Bar.html new file mode 100644 index 000000000000..e6f79984c4cd --- /dev/null +++ b/test/runtime/samples/dynamic-component-events/Bar.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-events/Foo.html b/test/runtime/samples/dynamic-component-events/Foo.html new file mode 100644 index 000000000000..3601714a5586 --- /dev/null +++ b/test/runtime/samples/dynamic-component-events/Foo.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-events/_config.js b/test/runtime/samples/dynamic-component-events/_config.js new file mode 100644 index 000000000000..3315382e9166 --- /dev/null +++ b/test/runtime/samples/dynamic-component-events/_config.js @@ -0,0 +1,27 @@ +export default { + data: { + x: true + }, + + html: ` + + `, + + test(assert, component, target, window) { + const click = new window.MouseEvent('click'); + + target.querySelector('button').dispatchEvent(click); + assert.equal(component.get('selected'), 'foo'); + + component.set({ + x: false + }); + + assert.htmlEqual(target.innerHTML, ` + + `); + + target.querySelector('button').dispatchEvent(click); + assert.equal(component.get('selected'), 'bar'); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-events/main.html b/test/runtime/samples/dynamic-component-events/main.html new file mode 100644 index 000000000000..8606b56ce069 --- /dev/null +++ b/test/runtime/samples/dynamic-component-events/main.html @@ -0,0 +1,12 @@ +<:Component { x ? Foo : Bar } on:select='set({ selected: event.id })'/> + + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-slot/Bar.html b/test/runtime/samples/dynamic-component-slot/Bar.html new file mode 100644 index 000000000000..76cea7cb67e1 --- /dev/null +++ b/test/runtime/samples/dynamic-component-slot/Bar.html @@ -0,0 +1,3 @@ +

Bar

+ + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-slot/Baz.html b/test/runtime/samples/dynamic-component-slot/Baz.html new file mode 100644 index 000000000000..9c7f92262c33 --- /dev/null +++ b/test/runtime/samples/dynamic-component-slot/Baz.html @@ -0,0 +1 @@ +
baz
\ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-slot/Foo.html b/test/runtime/samples/dynamic-component-slot/Foo.html new file mode 100644 index 000000000000..45e4d0001fa5 --- /dev/null +++ b/test/runtime/samples/dynamic-component-slot/Foo.html @@ -0,0 +1,3 @@ +

Foo

+ + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-slot/_config.js b/test/runtime/samples/dynamic-component-slot/_config.js new file mode 100644 index 000000000000..1b47436cb1de --- /dev/null +++ b/test/runtime/samples/dynamic-component-slot/_config.js @@ -0,0 +1,37 @@ +export default { + data: { + x: true + }, + + html: ` +

Foo

+
what goes up must come down
+

element

+ you're it +

neither foo nor bar

+ text + a + b + c +
baz
+ `, + + test(assert, component, target) { + component.set({ + x: false + }); + + assert.htmlEqual(target.innerHTML, ` +

Bar

+

element

+ you're it +

neither foo nor bar

+ text + a + b + c +
baz
+
what goes up must come down
+ `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-slot/main.html b/test/runtime/samples/dynamic-component-slot/main.html new file mode 100644 index 000000000000..fd4fad13691b --- /dev/null +++ b/test/runtime/samples/dynamic-component-slot/main.html @@ -0,0 +1,45 @@ +<:Component { x ? Foo : Bar } x='{{x}}'> +

element

+ + {{tag}} + + {{#if foo}} +

foo

+ {{elseif bar}} +

bar

+ {{else}} +

neither foo nor bar

+ {{/if}} + + text + + {{#each things as thing}} + {{thing}} + {{/each}} + + + +
what goes up must come down
+ + + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-update-existing-instance/Bar.html b/test/runtime/samples/dynamic-component-update-existing-instance/Bar.html new file mode 100644 index 000000000000..80328a2e8ad4 --- /dev/null +++ b/test/runtime/samples/dynamic-component-update-existing-instance/Bar.html @@ -0,0 +1 @@ +

Bar {{x}}

\ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-update-existing-instance/Foo.html b/test/runtime/samples/dynamic-component-update-existing-instance/Foo.html new file mode 100644 index 000000000000..fcb10c0f99c5 --- /dev/null +++ b/test/runtime/samples/dynamic-component-update-existing-instance/Foo.html @@ -0,0 +1 @@ +

Foo {{x}}

\ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-update-existing-instance/_config.js b/test/runtime/samples/dynamic-component-update-existing-instance/_config.js new file mode 100644 index 000000000000..d614cf82c5db --- /dev/null +++ b/test/runtime/samples/dynamic-component-update-existing-instance/_config.js @@ -0,0 +1,19 @@ +export default { + data: { + x: 1 + }, + + html: ` +

Foo 1

+ `, + + test(assert, component, target) { + component.set({ + x: 2 + }); + + assert.htmlEqual(target.innerHTML, ` +

Foo 2

+ `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component-update-existing-instance/main.html b/test/runtime/samples/dynamic-component-update-existing-instance/main.html new file mode 100644 index 000000000000..127c189cc316 --- /dev/null +++ b/test/runtime/samples/dynamic-component-update-existing-instance/main.html @@ -0,0 +1,12 @@ +<:Component { x ? Foo : Bar } x='{{x}}'/> + + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component/Bar.html b/test/runtime/samples/dynamic-component/Bar.html new file mode 100644 index 000000000000..7c21e68c89ff --- /dev/null +++ b/test/runtime/samples/dynamic-component/Bar.html @@ -0,0 +1 @@ +

{{x}}, therefore Bar

\ No newline at end of file diff --git a/test/runtime/samples/dynamic-component/Foo.html b/test/runtime/samples/dynamic-component/Foo.html new file mode 100644 index 000000000000..2303a92c0a11 --- /dev/null +++ b/test/runtime/samples/dynamic-component/Foo.html @@ -0,0 +1 @@ +

{{x}}, therefore Foo

\ No newline at end of file diff --git a/test/runtime/samples/dynamic-component/_config.js b/test/runtime/samples/dynamic-component/_config.js new file mode 100644 index 000000000000..915ce25717a3 --- /dev/null +++ b/test/runtime/samples/dynamic-component/_config.js @@ -0,0 +1,19 @@ +export default { + data: { + x: true + }, + + html: ` +

true, therefore Foo

+ `, + + test(assert, component, target) { + component.set({ + x: false + }); + + assert.htmlEqual(target.innerHTML, ` +

false, therefore Bar

+ `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/dynamic-component/main.html b/test/runtime/samples/dynamic-component/main.html new file mode 100644 index 000000000000..127c189cc316 --- /dev/null +++ b/test/runtime/samples/dynamic-component/main.html @@ -0,0 +1,12 @@ +<:Component { x ? Foo : Bar } x='{{x}}'/> + + \ No newline at end of file