diff --git a/packages/ember-htmlbars/lib/glimmer-component.js b/packages/ember-htmlbars/lib/glimmer-component.js index e6120f76f2d..5060b8160b1 100644 --- a/packages/ember-htmlbars/lib/glimmer-component.js +++ b/packages/ember-htmlbars/lib/glimmer-component.js @@ -8,7 +8,7 @@ import AriaRoleSupport from 'ember-views/mixins/aria_role_support'; import ViewMixin from 'ember-views/mixins/view_support'; import EmberView from 'ember-views/views/view'; -export default CoreView.extend( +const GlimmerComponent = CoreView.extend( ViewChildViewsSupport, ViewStateSupport, TemplateRenderingSupport, @@ -24,3 +24,9 @@ export default CoreView.extend( this._viewRegistry = this._viewRegistry || EmberView.views; } }); + +GlimmerComponent.reopenClass({ + isGlimmerComponentFactory: true +}); + +export default GlimmerComponent; diff --git a/packages/ember-htmlbars/lib/hooks/component.js b/packages/ember-htmlbars/lib/hooks/component.js index b5ae33b6d34..ef1e2da1758 100644 --- a/packages/ember-htmlbars/lib/hooks/component.js +++ b/packages/ember-htmlbars/lib/hooks/component.js @@ -24,12 +24,14 @@ var IS_ANGLE_CACHE = new Cache(1000, function(key) { export default function componentHook(renderNode, env, scope, _tagName, params, attrs, templates, visitor) { var state = renderNode.getState(); + let isClosureComponent = false; + let tagName = _tagName; if (CONTAINS_DOT_CACHE.get(tagName)) { - let stream = env.hooks.get(env, scope, tagName); - let componentCell = stream.value(); + let componentCell = getComponentCell(env, scope, tagName); if (isComponentCell(componentCell)) { + isClosureComponent = true; tagName = componentCell[COMPONENT_PATH]; /* @@ -62,10 +64,12 @@ export default function componentHook(renderNode, env, scope, _tagName, params, let isTopLevel = false; let isDasherized = false; - let angles = IS_ANGLE_CACHE.get(tagName); + let angles = IS_ANGLE_CACHE.get(_tagName); if (angles) { - tagName = angles[2]; + if (!isClosureComponent) { + tagName = angles[2]; + } isAngleBracket = true; isTopLevel = !!angles[1]; } @@ -164,3 +168,15 @@ export default function componentHook(renderNode, env, scope, _tagName, params, manager.render(env, visitor); } } + +function getComponentCell(env, scope, _tagName) { + let tagName = _tagName; + + // Clean tagName if it is a glimmer component + if (tagName.charAt(0) === '<') { + tagName = tagName.substring(1, tagName.length - 1); + } + + let stream = env.hooks.get(env, scope, tagName); + return stream.value(); +} diff --git a/packages/ember-htmlbars/tests/helpers/closure_component_test.js b/packages/ember-htmlbars/tests/helpers/closure_component_test.js index ba776547acc..f8a697d3df3 100644 --- a/packages/ember-htmlbars/tests/helpers/closure_component_test.js +++ b/packages/ember-htmlbars/tests/helpers/closure_component_test.js @@ -1,6 +1,7 @@ import { runAppend, runDestroy } from 'ember-runtime/tests/utils'; import ComponentLookup from 'ember-views/component_lookup'; import Component from 'ember-views/components/component'; +import GlimmerComponent from 'ember-htmlbars/glimmer-component'; import compile from 'ember-template-compiler/system/compile'; import run from 'ember-metal/run_loop'; import isEnabled from 'ember-metal/features'; @@ -26,76 +27,30 @@ if (isEnabled('ember-contextual-components')) { } }); - QUnit.test('renders with component helper', function() { + QUnit.test('[Component] renders with component helper', function() { let expectedText = 'Hodi'; - owner.register( - 'template:components/-looked-up', - compile(expectedText) - ); - - let template = compile('{{component (component "-looked-up")}}'); - component = Component.extend({ - [OWNER]: owner, - template - }).create(); - runAppend(component); - equal(component.$().text(), expectedText, '-looked-up component rendered'); - }); - - QUnit.test('renders with component helper with invocation params, hash', function() { - let LookedUp = Component.extend(); - LookedUp.reopenClass({ - positionalParams: ['name'] - }); owner.register( 'component:-looked-up', - LookedUp - ); - owner.register( - 'template:components/-looked-up', - compile(`{{greeting}} {{name}}`) - ); - - let template = compile( - `{{component (component "-looked-up") "Hodari" greeting="Hodi"}}` + Component.extend() ); - component = Component.extend({ - [OWNER]: owner, - template - }).create(); - - runAppend(component); - equal(component.$().text(), 'Hodi Hodari', '-looked-up component rendered'); - }); - QUnit.test('renders with component helper with curried params, hash', function() { - let LookedUp = Component.extend(); - LookedUp.reopenClass({ - positionalParams: ['name'] - }); - owner.register( - 'component:-looked-up', - LookedUp - ); owner.register( 'template:components/-looked-up', - compile(`{{greeting}} {{name}}`) + compile(expectedText) ); - let template = compile( - `{{component (component "-looked-up" "Hodari" greeting="Hodi") greeting="Hola"}}` - ); + let template = compile('{{component (component "-looked-up")}}'); component = Component.extend({ [OWNER]: owner, template }).create(); runAppend(component); - equal(component.$().text(), 'Hola Hodari', '-looked-up component rendered'); + equal(component.$().text(), expectedText, '-looked-up component rendered'); }); - QUnit.test('updates when component path is bound', function() { + QUnit.test('[Component] updates when component path is bound', function() { let Mandarin = Component.extend(); owner.register( 'component:-mandarin', @@ -103,11 +58,11 @@ if (isEnabled('ember-contextual-components')) { ); owner.register( 'template:components/-mandarin', - compile(`ni hao`) + compile('ni hao') ); owner.register( 'template:components/-hindi', - compile(`Namaste`) + compile('Namaste') ); let template = compile('{{component (component lookupComponent)}}'); @@ -121,6 +76,7 @@ if (isEnabled('ember-contextual-components')) { equal(component.$().text(), `ni hao`, 'mandarin lookupComponent renders greeting'); + run(() => { component.set('lookupComponent', '-hindi'); }); @@ -128,14 +84,16 @@ if (isEnabled('ember-contextual-components')) { 'hindi lookupComponent renders greeting'); }); - QUnit.test('updates when curried hash argument is bound', function() { + QUnit.test('updates when curried hash arguments is bound in block form', function() { owner.register( 'template:components/-looked-up', compile(`{{greeting}}`) ); let template = compile( - `{{component (component "-looked-up" greeting=greeting)}}` + `{{#with (hash comp=(component "-looked-up" greeting=greeting)) as |my|}} + {{#my.comp}}{{/my.comp}} + {{/with}}` ); component = Component.extend({ @@ -144,24 +102,27 @@ if (isEnabled('ember-contextual-components')) { }).create(); runAppend(component); - equal(component.$().text(), '', '-looked-up component rendered'); + equal(component.$().text().trim(), '', '-looked-up component rendered'); run(() => { component.set('greeting', 'Hodi'); }); - equal(component.$().text(), `Hodi`, + equal(component.$().text().trim(), `Hodi`, 'greeting is bound'); }); - QUnit.test('updates when curried hash arguments is bound in block form', function() { + QUnit.test('[Component] updates when curried hash argument is bound', function() { + owner.register( + 'component:-lookup-up', + Component.extend() + ); + owner.register( 'template:components/-looked-up', compile(`{{greeting}}`) ); let template = compile( - `{{#with (hash comp=(component "-looked-up" greeting=greeting)) as |my|}} - {{#my.comp}}{{/my.comp}} - {{/with}}` + `{{component (component "-looked-up" greeting=greeting)}}` ); component = Component.extend({ @@ -170,69 +131,50 @@ if (isEnabled('ember-contextual-components')) { }).create(); runAppend(component); - equal(component.$().text().trim(), '', '-looked-up component rendered'); + equal(component.$().text(), '', '-looked-up component rendered'); run(() => { component.set('greeting', 'Hodi'); }); - equal(component.$().text().trim(), `Hodi`, + equal(component.$().text(), `Hodi`, 'greeting is bound'); }); - QUnit.test('nested components overwrites named positional parameters', function() { - let LookedUp = Component.extend(); - LookedUp.reopenClass({ - positionalParams: ['name', 'age'] - }); - owner.register( - 'component:-looked-up', - LookedUp - ); - owner.register( - 'template:components/-looked-up', - compile(`{{name}} {{age}}`) - ); - - let template = compile( - `{{component - (component (component "-looked-up" "Sergio" 28) - "Marvin" 21) - "Hodari"}}` - ); - + QUnit.test('[Component] raises an assertion when component path is null', function() { + let template = compile(`{{component (component lookupComponent)}}`); component = Component.extend({ [OWNER]: owner, template }).create(); - runAppend(component); - equal(component.$().text(), 'Hodari 21', '-looked-up component rendered'); + expectAssertion(() => { + runAppend(component); + }); }); - QUnit.test('nested components overwrites hash parameters', function() { + QUnit.test('[Component] renders with dot path', function() { + let expectedText = 'Hodi'; + owner.register( - 'template:components/-looked-up', - compile(`{{greeting}} {{name}} {{age}}`) + 'component:-looked-up', + Component.extend() ); - let template = compile( - `{{component (component (component "-looked-up" - greeting="Hola" name="Dolores" age=33) - greeting="Hej" name="Sigmundur") - greeting=greeting}}` + owner.register( + 'template:components/-looked-up', + compile(`
${expectedText}
`) ); + let template = compile('{{#with (hash lookedup=(component "-looked-up")) as |object|}}{{object.lookedup}}{{/with}}'); component = Component.extend({ [OWNER]: owner, - template, - greeting: 'Hodi' + template }).create(); runAppend(component); - - equal(component.$().text(), 'Hodi Sigmundur 33', '-looked-up component rendered'); + equal(component.$().text(), expectedText, '-looked-up component rendered'); }); - QUnit.test('bound outer named parameters get updated in the right scope', function() { + QUnit.test('[Component] bound outer named parameters get updated in the right scope', function() { let InnerComponent = Component.extend(); InnerComponent.reopenClass({ positionalParams: ['comp'] @@ -273,7 +215,7 @@ if (isEnabled('ember-contextual-components')) { equal(component.$().text(), 'Inner 28', '-looked-up component rendered'); }); - QUnit.test('bound outer hash parameters get updated in the right scope', function() { + QUnit.test('[Component] bound outer hash parameters get updated in the right scope', function() { let InnerComponent = Component.extend(); InnerComponent.reopenClass({ positionalParams: ['comp'] @@ -313,7 +255,7 @@ if (isEnabled('ember-contextual-components')) { equal(component.$().text(), 'Inner 28', '-looked-up component rendered'); }); - QUnit.test('conflicting positional and hash parameters raise and assertion if in the same closure', function() { + QUnit.test('[Component] conflicting positional and hash parameters raise and assertion if in the same closure', function() { let LookedUp = Component.extend(); LookedUp.reopenClass({ positionalParams: ['name'] @@ -340,7 +282,7 @@ if (isEnabled('ember-contextual-components')) { }, `You cannot specify both a positional param (at position 0) and the hash argument \`name\`.`); }); - QUnit.test('conflicting positional and hash parameters does not raise and assertion if rerendered', function() { + QUnit.test('[Component] conflicting positional and hash parameters does not raise and assertion if rerendered', function() { let LookedUp = Component.extend(); LookedUp.reopenClass({ positionalParams: ['name'] @@ -372,7 +314,7 @@ if (isEnabled('ember-contextual-components')) { equal(component.$().text(), 'Hodi Sergio', 'component is rendered'); }); - QUnit.test('conflicting positional and hash parameters does not raise and assertion if in the different closure', function() { + QUnit.test('[Component] conflicting positional and hash parameters does not raise and assertion if in the different closure', function() { let LookedUp = Component.extend(); LookedUp.reopenClass({ positionalParams: ['name'] @@ -398,60 +340,14 @@ if (isEnabled('ember-contextual-components')) { equal(component.$().text(), 'Hodi Sergio', 'component is rendered'); }); - QUnit.test('raises an assertion when component path is null', function() { - let template = compile(`{{component (component lookupComponent)}}`); - component = Component.extend({ - [OWNER]: owner, - template - }).create(); - - expectAssertion(() => { - runAppend(component); - }); - }); - - QUnit.test('raises an assertion when component path is not a component name', function() { - let template = compile(`{{component (component "not-a-component")}}`); - component = Component.extend({ - [OWNER]: owner, - template - }).create(); - - expectAssertion(() => { - runAppend(component); - }, `The component helper cannot be used without a valid component name. You used "not-a-component" via (component "not-a-component")`); - - template = compile(`{{component (component compName)}}`); - component = Component.extend({ - [OWNER]: owner, - template, - compName: 'not-a-component' - }).create(); - - expectAssertion(() => { - runAppend(component); - }, `The component helper cannot be used without a valid component name. You used "not-a-component" via (component compName)`); - }); - - QUnit.test('renders with dot path', function() { + QUnit.test('[Component] renders with dot path and attr', function() { let expectedText = 'Hodi'; + owner.register( - 'template:components/-looked-up', - compile(expectedText) + 'component:-looked-up', + Component.extend() ); - let template = compile('{{#with (hash lookedup=(component "-looked-up")) as |object|}}{{object.lookedup}}{{/with}}'); - component = Component.extend({ - [OWNER]: owner, - template - }).create(); - - runAppend(component); - equal(component.$().text(), expectedText, '-looked-up component rendered'); - }); - - QUnit.test('renders with dot path and attr', function() { - let expectedText = 'Hodi'; owner.register( 'template:components/-looked-up', compile('{{expectedText}}') @@ -469,8 +365,14 @@ if (isEnabled('ember-contextual-components')) { equal(component.$().text(), expectedText, '-looked-up component rendered'); }); - QUnit.test('renders with dot path curried over attr', function() { + QUnit.test('[Component] renders with dot path curried over attr', function() { let expectedText = 'Hodi'; + + owner.register( + 'component:-looked-up', + Component.extend() + ); + owner.register( 'template:components/-looked-up', compile('{{expectedText}}') @@ -488,7 +390,7 @@ if (isEnabled('ember-contextual-components')) { equal(component.$().text(), expectedText, '-looked-up component rendered'); }); - QUnit.test('renders with dot path and with rest positional parameters', function() { + QUnit.test('[Component] renders with dot path and with rest positional parameters', function() { let LookedUp = Component.extend(); LookedUp.reopenClass({ positionalParams: 'params' @@ -515,7 +417,7 @@ if (isEnabled('ember-contextual-components')) { equal(component.$().text(), `${expectedText},Hola`, '-looked-up component rendered with rest params'); }); - QUnit.test('renders with dot path and rest parameter does not leak', function() { + QUnit.test('[Component] renders with dot path and rest parameter does not leak', function() { let value = false; let MyComponent = Component.extend({ didReceiveAttrs() { @@ -548,12 +450,13 @@ if (isEnabled('ember-contextual-components')) { ok(isEmpty(value), 'value is an empty parameter'); }); - QUnit.test('renders with dot path and updates attributes', function() { + QUnit.test('[Component] renders with dot path and updates attributes', function() { owner.register( 'component:my-nested-component', Component.extend({ didReceiveAttrs() { - this.set('myProp', this.getAttr('my-parent-attr')); + let prop = this.getAttr('my-parent-attr'); + this.set('myProp', prop); } }) ); @@ -593,7 +496,7 @@ if (isEnabled('ember-contextual-components')) { equal(component.$('#nested-prop').text(), '3', 'value got updated again'); }); - QUnit.test('adding parameters to a closure component\'s instance does not add it to other instances', function(assert) { + QUnit.test('[Component] adding parameters to a closure component\'s instance does not add it to other instances', function(assert) { owner.register( 'template:components/select-box', compile('{{yield (hash option=(component "select-box-option"))}}') @@ -616,4 +519,266 @@ if (isEnabled('ember-contextual-components')) { runAppend(component); equal(component.$().text(), 'Foo', 'there is only one Foo'); }); + + QUnit.test('[Component] raises an assertion when component path is not a component name', function() { + let template = compile(`{{component (component "not-a-component")}}`); + component = Component.extend({ + [OWNER]: owner, + template + }).create(); + + expectAssertion(() => { + runAppend(component); + }, `The component helper cannot be used without a valid component name. You used "not-a-component" via (component "not-a-component")`); + + template = compile(`{{component (component compName)}}`); + component = Component.extend({ + [OWNER]: owner, + template, + compName: 'not-a-component' + }).create(); + + expectAssertion(() => { + runAppend(component); + }, `The component helper cannot be used without a valid component name. You used "not-a-component" via (component compName)`); + }); + + QUnit.test('[Component] renders with component helper with invocation params, hash', function() { + let LookedUp = Component.extend(); + LookedUp.reopenClass({ + positionalParams: ['name'] + }); + owner.register( + 'component:-looked-up', + LookedUp + ); + owner.register( + 'template:components/-looked-up', + compile(`
{{greeting}} {{name}}
`) + ); + + let template = compile( + `{{component (component "-looked-up") "Hodari" greeting="Hodi"}}` + ); + component = Component.extend({ + [OWNER]: owner, + template + }).create(); + + runAppend(component); + equal(component.$().text(), 'Hodi Hodari', '-looked-up component rendered'); + }); + + QUnit.test('[Component] renders with component helper with curried params, hash', function() { + let LookedUp = Component.extend(); + LookedUp.reopenClass({ + positionalParams: ['name'] + }); + owner.register( + 'component:-looked-up', + LookedUp + ); + owner.register( + 'template:components/-looked-up', + compile(`{{greeting}} {{name}}`) + ); + + let template = compile( + `{{component (component "-looked-up" "Hodari" greeting="Hodi") greeting="Hola"}}` + ); + component = Component.extend({ + [OWNER]: owner, + template + }).create(); + + runAppend(component); + equal(component.$().text(), 'Hola Hodari', '-looked-up component rendered'); + }); + + QUnit.test('[Component] nested components overwrites named positional parameters', function() { + let LookedUp = Component.extend(); + LookedUp.reopenClass({ + positionalParams: ['name', 'age'] + }); + owner.register( + 'component:-looked-up', + LookedUp + ); + owner.register( + 'template:components/-looked-up', + compile(`{{name}} {{age}}`) + ); + + let template = compile( + `{{component + (component (component "-looked-up" "Sergio" 28) + "Marvin" 21) + "Hodari"}}` + ); + + component = Component.extend({ + [OWNER]: owner, + template + }).create(); + + runAppend(component); + equal(component.$().text(), 'Hodari 21', '-looked-up component rendered'); + }); + + QUnit.test('[Component] nested components overwrites hash parameters', function() { + owner.register( + 'component:-looked-up', + Component.extend() + ); + + owner.register( + 'template:components/-looked-up', + compile(`{{greeting}} {{name}} {{age}}`) + ); + + let template = compile( + `{{component (component (component "-looked-up" + greeting="Hola" name="Dolores" age=33) + greeting="Hej" name="Sigmundur") + greeting=greeting}}` + ); + + component = Component.extend({ + [OWNER]: owner, + template, + greeting: 'Hodi' + }).create(); + + runAppend(component); + + equal(component.$().text(), 'Hodi Sigmundur 33', '-looked-up component rendered'); + }); + + if (isEnabled('ember-htmlbars-component-generation')) { + QUnit.test('[GlimmerComponent] it does not render with component helper', function() { + owner.register( + 'component:-looked-up', + GlimmerComponent.extend() + ); + + owner.register( + 'template:components/-looked-up', + compile('
Hola
') + ); + + let template = compile('{{component (component "-looked-up")}}'); + component = Component.extend({ + [OWNER]: owner, + template + }).create(); + + expectAssertion(function() { + runAppend(component); + }, /curly braces/); + }); + + QUnit.test('[GlimmerComponent] it does not render with dot path and curly braces', function() { + owner.register( + 'component:-looked-up', + GlimmerComponent.extend() + ); + + owner.register( + 'template:components/-looked-up', + compile('
Hola
') + ); + + let template = compile(`{{#with (hash comp=(component "-looked-up")) as |my|}} + {{my.comp}} + {{/with}}`); + + component = Component.extend({ + [OWNER]: owner, + template + }).create(); + + expectAssertion(function() { + runAppend(component); + }, /curly braces/); + }); + + QUnit.test('[GlimmerComponent] it renders with angle brackets', function() { + owner.register( + 'component:-looked-up', + GlimmerComponent.extend() + ); + + owner.register( + 'template:components/-looked-up', + compile('
Hola
') + ); + + let template = compile(`{{#with (hash comp=(component "-looked-up")) as |my|}} + + {{/with}}`); + + component = Component.extend({ + [OWNER]: owner, + template + }).create(); + + runAppend(component); + + equal(component.$().text().trim(), 'Hola', 'component gets rendered'); + }); + + QUnit.test('[GlimmerComponent] it receives the attributes', function() { + owner.register('component:my-glimmer-component', GlimmerComponent.extend({ + layout: compile('{{value}}'), + didReceiveAttrs() { + this._super(...arguments); + console.log(this.attrs); + this.set(`value`, this.attrs.value); + } + })); + let template = compile(`{{#with (hash glimmer=(component "my-glimmer-component")) as |comp|}} + + {{/with}}`); + + component = Component.extend({ + [OWNER]: owner, + val: 12, + template + }).create(); + + runAppend(component); + + equal(component.$().text().trim(), '12', 'component gets the right value'); + }); + + QUnit.test('[GlimmerComponent] it updates the attributes', function() { + owner.register('component:my-glimmer-component', GlimmerComponent.extend({ + layout: compile('{{value}}'), + didReceiveAttrs() { + this._super(...arguments); + console.log(this.attrs); + this.set(`value`, this.attrs.value); + } + })); + let template = compile(`{{#with (hash glimmer=(component "my-glimmer-component")) as |comp|}} + + {{/with}}`); + + component = Component.extend({ + [OWNER]: owner, + val: 12, + template + }).create(); + + runAppend(component); + + equal(component.$().text().trim(), '12', 'component gets the right value'); + + run(() => { + component.set('val', 14); + }); + + equal(component.$().text().trim(), '14', 'attribute got updated'); + }); + } }