diff --git a/packages/container/lib/container.js b/packages/container/lib/container.js index 68b0026be9f..9a8f0a4beef 100644 --- a/packages/container/lib/container.js +++ b/packages/container/lib/container.js @@ -223,8 +223,8 @@ function wrapManagerInDeprecationProxy(manager) { return manager; } -function isSingleton(container, fullName) { - return container.registry.getOption(fullName, 'singleton') !== false; +function isSingleton(container, fullName, options) { + return container.registry.getOption(fullName, 'singleton', options) !== false; } function isInstantiatable(container, fullName) { @@ -278,7 +278,7 @@ function isFactoryInstance(container, fullName, { instantiate, singleton }) { } function instantiateFactory(container, fullName, options) { - let factoryManager = EMBER_MODULE_UNIFICATION && options && options.source ? container.factoryFor(fullName, options) : container.factoryFor(fullName); + let factoryManager = EMBER_MODULE_UNIFICATION && options ? container.factoryFor(fullName, options) : container.factoryFor(fullName); if (factoryManager === undefined) { return; @@ -316,9 +316,10 @@ function buildInjections(container, injections) { let injection; for (let i = 0; i < injections.length; i++) { injection = injections[i]; - hash[injection.property] = lookup(container, injection.fullName); + let {name, namespace, type} = parseInjectionString(injection.fullName); + hash[injection.property] = lookupWithRawString(container, type, namespace || name); if (!isDynamic) { - isDynamic = !isSingleton(container, injection.fullName); + isDynamic = !isSingleton(container, injection.fullName, { namespace }); } } } @@ -354,13 +355,15 @@ function resetCache(container) { container.factoryManagerCache = dictionary(null); } -function resetMember(container, fullName) { - let member = container.cache[fullName]; +function resetMember(container, fullName, options={}) { + let cacheKey = container._resolverCacheKey(fullName, options); - delete container.factoryManagerCache[fullName]; + let member = container.cache[cacheKey]; + + delete container.factoryManagerCache[cacheKey]; if (member) { - delete container.cache[fullName]; + delete container.cache[cacheKey]; if (member.destroy) { member.destroy(); @@ -440,3 +443,45 @@ class FactoryManager { return instance; } } + +export function parseInjectionString(injectionString) { + let typeDelimiterOffset = injectionString.indexOf(':'); + let type = injectionString.slice(0, typeDelimiterOffset); + let rawString = injectionString.slice(typeDelimiterOffset+1); + if (rawString.indexOf('::') === -1) { + return { + fullName: injectionString, + name: rawString, + type + }; + } else { + let [ namespace, name ] = rawString.split('::'); + let fullName = type.indexOf(':') === -1 ? `${type}:${name}` : `${type}${name}`; + return { + fullName, + namespace, + type + }; + } +} + +export function lookupWithRawString(container, type, rawString) { + if (rawString.indexOf('::') === -1) { + return container.lookup(`${type}:${rawString}`); + } else { + let [ namespace, name ] = rawString.split('::'); + let fullName = type.indexOf(':') === -1 ? `${type}:${name}` : `${type}${name}`; + return container.lookup(fullName, { namespace }); + } +} + +export function factoryForWithRawString(container, type, rawString) { + if (rawString.indexOf('::') === -1) { + return container.factoryFor(`${type}:${rawString}`); + } else { + let [ namespace, name ] = rawString.split('::'); + // type might already contain ":" eg. "template:components/" + let fullName = type.indexOf(':') === -1 ? `${type}:${name}` : `${type}${name}`; + return container.factoryFor(fullName, { namespace }); + } +} diff --git a/packages/container/lib/index.js b/packages/container/lib/index.js index 06d441265b2..46103731d56 100644 --- a/packages/container/lib/index.js +++ b/packages/container/lib/index.js @@ -9,4 +9,6 @@ export { default as Registry, privatize } from './registry'; export { default as Container, FACTORY_FOR, + factoryForWithRawString, + lookupWithRawString } from './container'; diff --git a/packages/container/lib/registry.js b/packages/container/lib/registry.js index 820d0554710..c9d178ac57d 100644 --- a/packages/container/lib/registry.js +++ b/packages/container/lib/registry.js @@ -1,7 +1,9 @@ import { dictionary, assign, intern } from 'ember-utils'; import { assert, deprecate } from 'ember-debug'; import { EMBER_MODULE_UNIFICATION } from 'ember/features'; -import Container from './container'; +import Container, { + parseInjectionString +} from './container'; import { DEBUG } from 'ember-env-flags'; import { ENV } from 'ember-environment'; @@ -151,11 +153,12 @@ export default class Registry { assert(`Attempting to register an unknown factory: '${fullName}'`, factory !== undefined); let normalizedName = this.normalize(fullName); - assert(`Cannot re-register: '${fullName}', as it has already been resolved.`, !this._resolveCache[normalizedName]); + let cacheKey = this.resolverCacheKey(normalizedName, options); + assert(`Cannot re-register: '${fullName}', as it has already been resolved.`, !this._resolveCache[cacheKey]); - this._failSet.delete(normalizedName); + this._failSet.delete(cacheKey); this.registrations[normalizedName] = factory; - this._options[normalizedName] = options; + this._options[cacheKey] = options; } /** @@ -179,13 +182,14 @@ export default class Registry { assert('fullName must be a proper full name', this.isValidFullName(fullName)); let normalizedName = this.normalize(fullName); + let cacheKey = this.resolverCacheKey(normalizedName); this._localLookupCache = Object.create(null); delete this.registrations[normalizedName]; - delete this._resolveCache[normalizedName]; - delete this._options[normalizedName]; - this._failSet.delete(normalizedName); + delete this._resolveCache[cacheKey]; + delete this._options[cacheKey]; + this._failSet.delete(cacheKey); } /** @@ -322,8 +326,9 @@ export default class Registry { } let source = options && options.source && this.normalize(options.source); + let namespace = options && options.namespace; - return has(this, this.normalize(fullName), source); + return has(this, this.normalize(fullName), source, namespace); } /** @@ -374,13 +379,13 @@ export default class Registry { @param {Object} options */ options(fullName, options = {}) { - let normalizedName = this.normalize(fullName); - this._options[normalizedName] = options; + let cacheKey = this.resolverCacheKey(this.normalize(fullName)); + this._options[cacheKey] = options; } getOptions(fullName) { - let normalizedName = this.normalize(fullName); - let options = this._options[normalizedName]; + let cacheKey = this.resolverCacheKey(this.normalize(fullName)); + let options = this._options[cacheKey]; if (options === undefined && this.fallback !== null) { options = this.fallback.getOptions(fullName); @@ -388,8 +393,8 @@ export default class Registry { return options; } - getOption(fullName, optionName) { - let options = this._options[fullName]; + getOption(fullName, optionName, __options={}) { + let options = this._options[this.resolverCacheKey(fullName, __options)]; if (options && options[optionName] !== undefined) { return options[optionName]; @@ -401,7 +406,7 @@ export default class Registry { if (options && options[optionName] !== undefined) { return options[optionName]; } else if (this.fallback !== null) { - return this.fallback.getOption(fullName, optionName); + return this.fallback.getOption(fullName, optionName, __options); } } @@ -566,12 +571,12 @@ export default class Registry { return injections; } - resolverCacheKey(name, options) { - if (!EMBER_MODULE_UNIFICATION) { - return name; - } - - return (options && options.source) ? `${options.source}:${name}` : name; + resolverCacheKey(name, options={}) { + return [ + name, + options.source, + options.namespace + ].join('\0'); } /** @@ -625,11 +630,15 @@ if (DEBUG) { for (let key in hash) { if (hash.hasOwnProperty(key)) { - assert(`Expected a proper full name, given '${hash[key]}'`, this.isValidFullName(hash[key])); + let injection = hash[key]; + let injectionString = (typeof injection === 'string') ? injection : injection.fullName; + let { fullName } = parseInjectionString(injectionString); + assert(`Expected a proper full name, given '${fullName}'`, this.isValidFullName(fullName)); injections.push({ property: key, - fullName: hash[key] + fullName: hash[key].fullName, + namespace: hash[key].namespace }); } } @@ -640,12 +649,17 @@ if (DEBUG) { Registry.prototype.validateInjections = function(injections) { if (!injections) { return; } - let fullName; - for (let i = 0; i < injections.length; i++) { - fullName = injections[i].fullName; + let injection = injections[i]; + let injectionString = (typeof injection === 'string') ? injection : injection.fullName; + let { + fullName, + namespace + } = parseInjectionString(injectionString); + + namespace = namespace || injection.namespace; - assert(`Attempting to inject an unknown injection: '${fullName}'`, this.has(fullName)); + assert(`Attempting to inject an unknown injection: '${fullName}'`, this.has(fullName, {namespace})); } }; } @@ -696,7 +710,7 @@ function resolve(registry, normalizedName, options) { let resolved; if (registry.resolver) { - resolved = registry.resolver.resolve(normalizedName, options && options.source); + resolved = registry.resolver.resolve(normalizedName, options && options.source, options && options.namespace); } if (resolved === undefined) { @@ -712,8 +726,11 @@ function resolve(registry, normalizedName, options) { return resolved; } -function has(registry, fullName, source) { - return registry.resolve(fullName, { source }) !== undefined; +function has(registry, fullName, source, namespace) { + return registry.resolve(fullName, { + source, + namespace + }) !== undefined; } const privateNames = dictionary(null); diff --git a/packages/container/tests/container_test.js b/packages/container/tests/container_test.js index a87a244dc66..aabc8b908d7 100644 --- a/packages/container/tests/container_test.js +++ b/packages/container/tests/container_test.js @@ -1,7 +1,16 @@ import { OWNER, assign } from 'ember-utils'; import { EMBER_MODULE_UNIFICATION } from 'ember/features'; -import { Registry } from '..'; -import { factory, moduleFor, AbstractTestCase } from 'internal-test-helpers'; +import { + factoryForWithRawString, + lookupWithRawString, + Registry +} from '..'; +import { + factory, + moduleFor, + AbstractTestCase, + ModuleBasedTestResolver +} from 'internal-test-helpers'; moduleFor('Container', class extends AbstractTestCase { ['@test A registered factory returns the same instance each time'](assert) { @@ -396,7 +405,8 @@ moduleFor('Container', class extends AbstractTestCase { Apple.reopenClass({ _lazyInjections() { - return ['orange:main', 'banana:main']; + return [{ fullName: 'orange:main' }, + { fullName: 'banana:main' }]; } }); @@ -419,7 +429,7 @@ moduleFor('Container', class extends AbstractTestCase { Apple.reopenClass({ _lazyInjections: () => { assert.ok(true, 'should call lazy injection method'); - return ['orange:main']; + return [{ fullName: 'orange:main' }]; } }); @@ -676,8 +686,133 @@ if (EMBER_MODULE_UNIFICATION) { let result = container.lookup(lookup, { source: expectedSource }); this.assert.ok(result instanceof PrivateComponent, 'The correct factory was provided'); - this.assert.ok(container.cache[`template:routes/application:component:my-input`] instanceof PrivateComponent, - 'The correct factory was stored in the cache with the correct key which includes the source.'); + this.assert.ok( + container.cache[`component:my-input\0template:routes/application\0`] instanceof PrivateComponent, + 'The correct factory was stored in the cache with the correct key which includes the source.' + ); + } + + ['@test The container can pass a namespaced path to factoryFor']() { + let PrivateComponent = factory(); + let type = 'component'; + let namespace = 'my-addon'; + let name = 'my-component'; + let rawString = `${namespace}::${name}`; + let specifier = `${type}:${name}`; + let resolver = new ModuleBasedTestResolver(); + let registry = new Registry({resolver}); + + resolver.add({ + specifier, + namespace + }, PrivateComponent); + + let container = registry.container(); + + this.assert.equal( + container.factoryFor(specifier), undefined, + 'Cannot find factoryFor by name' + ); + expectAssertion(() => { + container.factoryFor(`${type}:${rawString}`); + }, /must be a proper full name/, 'Cannot find factoryFor by rawString'); + + // test internal API of factoryForWithRawString + let result = factoryForWithRawString(container, 'component', rawString); + this.assert.strictEqual( + result.class, PrivateComponent, + 'The correct factory was provided by internal method factoryForWithRawString' + ); + this.assert.strictEqual( + result.class, PrivateComponent, + 'The correct factory was provided again by internal method factoryForWithRawString' + ); + + // test public API of passing namespace to factoryFor + result = container.factoryFor(specifier, { namespace }); + this.assert.strictEqual( + result.class, PrivateComponent, + 'The correct factory was provided' + ); + this.assert.strictEqual( + result.class, PrivateComponent, + 'The correct factory was provided again' + ); + } + + ['@test The container will not lookup a namespaced factory without namespace']() { + let PrivateComponent = factory(); + let type = 'component'; + let namespace = 'my-addon'; + let name = 'my-component'; + let rawString = `${namespace}::${name}`; + let specifier = `${type}:${name}`; + let resolver = new ModuleBasedTestResolver(); + let registry = new Registry({resolver}); + + resolver.add({ + specifier, + namespace + }, PrivateComponent); + + let container = registry.container(); + + this.assert.equal( + container.lookup(specifier), undefined, + 'Cannot lookup by type:name' + ); + expectAssertion(() => { + container.lookup(`${type}:${rawString}`); + }, /must be a proper full name/, 'Cannot lookup by type:rawString'); + } + + ['@test The container will lookup a namespaced factory with namespace via lookupWithRawString']() { + let PrivateComponent = factory(); + let type = 'component'; + let namespace = 'my-addon'; + let name = 'my-component'; + let rawString = `${namespace}::${name}`; + let specifier = `${type}:${name}`; + let resolver = new ModuleBasedTestResolver(); + let registry = new Registry({resolver}); + + resolver.add({ + specifier, + namespace + }, PrivateComponent); + + let container = registry.container(); + + let result = lookupWithRawString(container, 'component', rawString); + this.assert.ok(result instanceof PrivateComponent, 'The correct factory was provided by private API'); + this.assert.ok( + container.cache[`${specifier}\0\0${namespace}`] instanceof PrivateComponent, + 'The correct factory was stored in the cache with the correct key which includes the raw string.' + ); + } + + ['@test The container will lookup a namespaced factory with namespace via lookup']() { + let PrivateComponent = factory(); + let type = 'component'; + let namespace = 'my-addon'; + let name = 'my-component'; + let specifier = `${type}:${name}`; + let resolver = new ModuleBasedTestResolver(); + let registry = new Registry({resolver}); + + resolver.add({ + specifier, + namespace + }, PrivateComponent); + + let container = registry.container(); + + let result = container.lookup(specifier, { namespace }); + this.assert.ok(result instanceof PrivateComponent, 'The correct factory was provided'); + this.assert.ok( + container.cache[`${specifier}\0\0${namespace}`] instanceof PrivateComponent, + 'The correct factory was stored in the cache with the correct key which includes the raw string.' + ); } }); } diff --git a/packages/container/tests/registry_test.js b/packages/container/tests/registry_test.js index 30fba1eb3c9..4de0d520056 100644 --- a/packages/container/tests/registry_test.js +++ b/packages/container/tests/registry_test.js @@ -1,5 +1,5 @@ import { Registry, privatize } from '..'; -import { factory, moduleFor, AbstractTestCase } from 'internal-test-helpers'; +import { factory, moduleFor, AbstractTestCase, ModuleBasedTestResolver } from 'internal-test-helpers'; import { EMBER_MODULE_UNIFICATION } from 'ember/features'; import { ENV } from 'ember-environment'; @@ -746,25 +746,39 @@ if (EMBER_MODULE_UNIFICATION) { moduleFor('Registry module unification', class extends AbstractTestCase { ['@test The registry can pass a source to the resolver'](assert) { let PrivateComponent = factory(); - let lookup = 'component:my-input'; + let type = 'component'; + let name = 'my-component'; + let fullName = `${type}:${name}`; let source = 'template:routes/application'; - let resolveCount = 0; - let resolver = { - resolve(fullName, src) { - resolveCount++; - if (fullName === lookup && src === source) { - return PrivateComponent; - } - } - }; + let resolver = new ModuleBasedTestResolver(); let registry = new Registry({ resolver }); - registry.normalize = function(name) { - return name; - }; - assert.strictEqual(registry.resolve(lookup, { source }), PrivateComponent, 'The correct factory was provided'); - assert.strictEqual(registry.resolve(lookup, { source }), PrivateComponent, 'The correct factory was provided again'); - assert.equal(resolveCount, 1, 'resolve called only once and a cached factory was returned the second time'); + resolver.add({ + specifier: fullName, + referrer: source + }, PrivateComponent); + + assert.strictEqual(registry.resolve(fullName, { source }), PrivateComponent, 'The correct factory was provided'); + assert.strictEqual(registry.resolve(fullName, { source }), PrivateComponent, 'The correct factory was provided again'); + } + + ['@test The registry can pass a namespaced lookup to the resolver'](assert) { + let PrivateComponent = factory(); + let type = 'component'; + let namespace = 'my-addon'; + let name = 'my-component'; + let specifier = `${type}:${name}`; + let resolver = new ModuleBasedTestResolver(); + let registry = new Registry({ resolver }); + + resolver.add({ + specifier, + namespace + }, PrivateComponent); + + assert.strictEqual(registry.resolve(specifier, { namespace }), PrivateComponent, 'The correct factory was provided'); + assert.strictEqual(registry.resolve(specifier, { namespace }), PrivateComponent, 'The correct factory was provided again'); } }); + } diff --git a/packages/ember-glimmer/lib/resolver.ts b/packages/ember-glimmer/lib/resolver.ts index 556b900b40d..5ec2997d77a 100644 --- a/packages/ember-glimmer/lib/resolver.ts +++ b/packages/ember-glimmer/lib/resolver.ts @@ -11,7 +11,7 @@ import { Helper, ModifierManager, } from '@glimmer/runtime'; -import { privatize as P } from 'container'; +import { privatize as P, factoryForWithRawString } from 'container'; import { assert } from 'ember-debug'; import { ENV } from 'ember-environment'; import { _instrumentStart } from 'ember-metal'; @@ -218,7 +218,10 @@ export default class RuntimeResolver implements IRuntimeResolver this.rerender()); + + this.assertText('namespaced template My property'); + } + + ['@test it can render a nested namespaced component']() { + this.addTemplate({ + specifier: 'template:components/my-component', + namespace: 'second-addon' + }, 'second namespaced template'); + + this.addTemplate({ + specifier: 'template:components/my-component', + namespace: 'first-addon' + }, '{{second-addon::my-component}}'); + + this.addComponent('x-outer', { template: '{{first-addon::my-component}}' }); + + this.render('{{x-outer}}'); + + this.assertText('second namespaced template'); + + this.runTask(() => this.rerender()); + + this.assertText('second namespaced template'); + } + + ['@test it can render a nested un-namespaced component']() { + this.addTemplate({ + specifier: 'template:components/addon-component', + referrer: 'template:/first-addon/src/ui/components/my-component' + }, 'un-namespaced addon template'); + + this.addTemplate({ + specifier: 'template:components/my-component', + // TODO: moduleNames really should have type, be specifiers. + moduleName: '/first-addon/src/ui/components/my-component', + namespace: 'first-addon' + }, '{{addon-component}}'); + + this.addComponent('x-outer', { template: '{{first-addon::my-component}}' }); + + this.render('{{x-outer}}'); + + this.assertText('un-namespaced addon template'); + + this.runTask(() => this.rerender()); + + this.assertText('un-namespaced addon template'); + } + + ['@test it can render a namespaced main component']() { + this.addTemplate({ + specifier: 'template:components/addon-component', + referrer: 'template:/first-addon/src/ui/components/main' + }, 'Nested namespaced component'); + + this.addTemplate({ + specifier: 'template:components/first-addon', + moduleName: '/first-addon/src/ui/components/main' + }, '{{addon-component}}'); + + this.addComponent('x-outer', { template: '{{first-addon}}' }); + + this.render('{{x-outer}}'); + + this.assertText('Nested namespaced component'); + + this.runTask(() => this.rerender()); + + this.assertText('Nested namespaced component'); + } + + ['@test it does not render a main component when using a namespace']() { + this.addTemplate({ + specifier: 'template:components/main', + namespace: 'my-addon' + }, 'namespaced template {{myProp}}'); + + this.add({ + specifier: 'component:main', + namespace: 'my-addon' + }, Component.extend({ + myProp: 'My property' + })); + + this.add({ + specifier: 'helper:my-addon', + namespace: 'empty-namespace' + + }, helper(() => 'my helper')); + + this.render('{{empty-namespace::my-addon}}'); + + this.assertText('my helper'); // component should be not found + + this.runTask(() => this.rerender()); + + this.assertText('my helper'); + } + + ['@test it renders a namespaced helper']() { + this.add({ + specifier: 'helper:my-helper', + namespace: 'my-namespace' + }, helper(() => 'my helper')); + + this.render('{{my-namespace::my-helper}}'); + + this.assertText('my helper'); + + this.runTask(() => this.rerender()); + + this.assertText('my helper'); + } + }); +} diff --git a/packages/ember-metal/lib/injected_property.js b/packages/ember-metal/lib/injected_property.js index 698c1267434..2c6a4a6505b 100644 --- a/packages/ember-metal/lib/injected_property.js +++ b/packages/ember-metal/lib/injected_property.js @@ -4,6 +4,7 @@ import { ComputedProperty } from './computed'; import { AliasedProperty } from './alias'; import { Descriptor } from './properties'; import { descriptorFor } from './meta'; +import { lookupWithRawString } from 'container'; /** @module ember @@ -21,9 +22,10 @@ import { descriptorFor } from './meta'; to the property's name @private */ -export default function InjectedProperty(type, name) { +export default function InjectedProperty(type, name, options) { this.type = type; this.name = name; + this.options = options; this._super$Constructor(injectedPropertyGet); AliasedPropertyPrototype.oneWay.call(this); @@ -36,7 +38,10 @@ function injectedPropertyGet(keyName) { assert(`InjectedProperties should be defined with the inject computed property macros.`, desc && desc.type); assert(`Attempting to lookup an injected property on an object without a container, ensure that the object was instantiated via a container.`, owner); - return owner.lookup(`${desc.type}:${desc.name || keyName}`); + if (desc.options && desc.options.namespace) { // && (!desc.name || desc.name.indexOf('::') === -1) + return lookupWithRawString(owner, desc.type, `${desc.options.namespace}::${desc.name || keyName}`); + } + return lookupWithRawString(owner, desc.type, desc.name || keyName); } InjectedProperty.prototype = Object.create(Descriptor.prototype); diff --git a/packages/ember-runtime/lib/inject.js b/packages/ember-runtime/lib/inject.js index 1360e5f6263..58372840491 100644 --- a/packages/ember-runtime/lib/inject.js +++ b/packages/ember-runtime/lib/inject.js @@ -35,7 +35,7 @@ const typeValidators = {}; export function createInjectionHelper(type, validator) { typeValidators[type] = validator; - inject[type] = name => new InjectedProperty(type, name); + inject[type] = (name, options) => new InjectedProperty(type, name, options); } /** diff --git a/packages/ember-runtime/lib/system/core_object.js b/packages/ember-runtime/lib/system/core_object.js index 43f9452f540..515b70bd025 100644 --- a/packages/ember-runtime/lib/system/core_object.js +++ b/packages/ember-runtime/lib/system/core_object.js @@ -1057,7 +1057,10 @@ if (DEBUG) { for (key in proto) { desc = descriptorFor(proto, key); if (desc instanceof InjectedProperty) { - injections[key] = `${desc.type}:${desc.name || key}`; + injections[key] = { + fullName: `${desc.type}:${desc.name || key}`, + namespace: desc.options && desc.options.namespace + }; } } diff --git a/packages/ember-runtime/tests/inject_test.js b/packages/ember-runtime/tests/inject_test.js index fe3f68397bd..d2e92b3b558 100644 --- a/packages/ember-runtime/tests/inject_test.js +++ b/packages/ember-runtime/tests/inject_test.js @@ -61,6 +61,9 @@ if (DEBUG) { bar: new InjectedProperty('quux') }); - assert.deepEqual(AnObject._lazyInjections(), { 'foo': 'foo:bar', 'bar': 'quux:bar' }, 'should return injected container keys'); + assert.deepEqual(AnObject._lazyInjections(), { + 'foo': { fullName: 'foo:bar', namespace: undefined }, + 'bar': { fullName: 'quux:bar', namespace: undefined } + }, 'should return injected container keys'); }); } diff --git a/packages/ember-views/lib/component_lookup.js b/packages/ember-views/lib/component_lookup.js index e62af4fbc8f..44de91a305d 100644 --- a/packages/ember-views/lib/component_lookup.js +++ b/packages/ember-views/lib/component_lookup.js @@ -1,17 +1,29 @@ import { assert } from 'ember-debug'; import { Object as EmberObject } from 'ember-runtime'; +import { + factoryForWithRawString, + lookupWithRawString +} from 'container'; export default EmberObject.extend({ componentFor(name, owner, options) { assert(`You cannot use '${name}' as a component name. Component names must contain a hyphen.`, ~name.indexOf('-')); - let fullName = `component:${name}`; - return owner.factoryFor(fullName, options); + if (name.indexOf('::') === -1) { + let fullName = `component:${name}`; + return owner.factoryFor(fullName, options); + } else { + return factoryForWithRawString(owner, 'component', name); + } }, layoutFor(name, owner, options) { assert(`You cannot use '${name}' as a component name. Component names must contain a hyphen.`, ~name.indexOf('-')); - let templateFullName = `template:components/${name}`; - return owner.lookup(templateFullName, options); + if (name.indexOf('::') === -1) { + let templateFullName = `template:components/${name}`; + return owner.lookup(templateFullName, options); + } else { + return lookupWithRawString(owner, 'template:components/', name); + } } }); diff --git a/packages/ember/tests/service_injection_test.js b/packages/ember/tests/service_injection_test.js index 675ef04bef2..a6e6ba8093f 100644 --- a/packages/ember/tests/service_injection_test.js +++ b/packages/ember/tests/service_injection_test.js @@ -2,7 +2,7 @@ import { Controller } from 'ember-runtime'; import { moduleFor, ApplicationTestCase } from 'internal-test-helpers'; import { inject, Service } from 'ember-runtime'; import { computed } from 'ember-metal'; -import { EMBER_METAL_ES5_GETTERS } from 'ember/features'; +import { EMBER_METAL_ES5_GETTERS, EMBER_MODULE_UNIFICATION } from 'ember/features'; moduleFor('Service Injection', class extends ApplicationTestCase { @@ -43,3 +43,41 @@ if (EMBER_METAL_ES5_GETTERS) { } }); } + +if (EMBER_MODULE_UNIFICATION) { + moduleFor('Service Injection (MU)', class extends ApplicationTestCase { + ['@test Service with namespace can be injected and is resolved'](assert) { + this.add('controller:application', Controller.extend({ + myService: inject.service('my-namespace::my-service') + })); + let MyService = Service.extend(); + this.add({ + specifier: 'service:my-service', + namespace: 'my-namespace' + }, MyService); + + this.visit('/').then(() => { + let controller = this.applicationInstance.lookup('controller:application'); + assert.ok(controller.get('myService') instanceof MyService); + }); + } + + ['@test Service with namespace can be injected with namespace and is resolved'](assert) { + let namespace = 'my-namespace'; + this.add('controller:application', Controller.extend({ + myService: inject.service('my-service', { namespace }) + })); + let MyService = Service.extend(); + this.add({ + specifier: 'service:my-service', + namespace + }, MyService); + + this.visit('/').then(() => { + let controller = this.applicationInstance.lookup('controller:application'); + + assert.ok(controller.get('myService') instanceof MyService); + }); + } + }); +} diff --git a/packages/internal-test-helpers/lib/test-cases/abstract-rendering.js b/packages/internal-test-helpers/lib/test-cases/abstract-rendering.js index 26390a55b50..1c8dd807a18 100644 --- a/packages/internal-test-helpers/lib/test-cases/abstract-rendering.js +++ b/packages/internal-test-helpers/lib/test-cases/abstract-rendering.js @@ -2,6 +2,7 @@ import { assign } from 'ember-utils'; import { compile } from 'ember-template-compiler'; import { EventDispatcher } from 'ember-views'; import { helper, Helper, Component, _resetRenderers} from 'ember-glimmer'; +import { ModuleBasedResolver } from '../test-resolver'; import AbstractTestCase from './abstract'; import buildOwner from '../build-owner'; @@ -41,7 +42,42 @@ export default class AbstractRenderingTestCase extends AbstractTestCase { getOwnerOptions() { } getBootOptions() { } - getResolver() { } + + get resolver() { + return this.owner.__registry__.fallback.resolver; + } + + getResolver() { + return new ModuleBasedResolver(); + } + + add(specifier, factory) { + this.resolver.add(specifier, factory); + } + + addTemplate(templateName, templateString) { + if (typeof templateName === 'string') { + this.resolver.add(`template:${templateName}`, this.compile(templateString, { + moduleName: templateName + })); + } else { + this.resolver.add(templateName, this.compile(templateString, { + moduleName: templateName.moduleName + })); + } + } + + addComponent(name, { ComponentClass = null, template = null }) { + if (ComponentClass) { + this.resolver.add(`component:${name}`, ComponentClass); + } + + if (typeof template === 'string') { + this.resolver.add(`template:components/${name}`, this.compile(template, { + moduleName: `components/${name}` + })); + } + } afterEach() { try { diff --git a/packages/internal-test-helpers/lib/test-resolver.js b/packages/internal-test-helpers/lib/test-resolver.js index 44e87a28271..1489738a663 100644 --- a/packages/internal-test-helpers/lib/test-resolver.js +++ b/packages/internal-test-helpers/lib/test-resolver.js @@ -1,25 +1,43 @@ import { compile } from 'ember-template-compiler'; +const DELIMITER = '\0'; + +function serializeKey(specifier, referrer, namespace) { + return [specifier, referrer, namespace].join(DELIMITER); +} + class Resolver { constructor() { this._registered = {}; this.constructor.lastInstance = this; } - resolve(specifier) { - return this._registered[specifier]; + resolve(specifier, referrer, namespace) { + return this._registered[serializeKey(specifier, referrer, namespace)]; } add(specifier, factory) { - if (specifier.indexOf(':') === -1) { - throw new Error('Specifiers added to the resolver must be in the format of type:name'); + let key; + switch (typeof specifier) { + case 'string': + if (specifier.indexOf(':') === -1) { + throw new Error('Specifiers added to the resolver must be in the format of type:name'); + } + key = serializeKey(specifier); + break; + case 'object': + key = serializeKey(specifier.specifier, specifier.referrer, specifier.namespace); + break; + default: + throw new Error('Specifier string has an unknown type'); } - return this._registered[specifier] = factory; + + return this._registered[key] = factory; } addTemplate(templateName, template) { let templateType = typeof template; if (templateType !== 'string') { throw new Error(`You called addTemplate for "${templateName}" with a template argument of type of '${templateType}'. addTemplate expects an argument of an uncompiled template as a string.`); } - return this._registered[`template:${templateName}`] = compile(template, { + return this._registered[serializeKey(`template:${templateName}`)] = compile(template, { moduleName: templateName }); }