Skip to content

Commit

Permalink
Validate Ember object types in resolver
Browse files Browse the repository at this point in the history
This commit adds assertions to ensure that some key object types
are validated when they are resolved:

* Routes
* Components
* Views
* Services

I did not add a validation for controller classes because the
implementation for detection would need to be a little different and
some of Ember's tests play fast (and loose) with with the types of
things they try to pass off as controllers. For now I don't think this
additional validation is worth adding given imminent demise of
Controllers.
  • Loading branch information
mitchlloyd committed May 24, 2015
1 parent 37f4325 commit a876722
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 3 deletions.
5 changes: 5 additions & 0 deletions packages/ember-application/lib/system/resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import EmberObject from 'ember-runtime/system/object';
import Namespace from 'ember-runtime/system/namespace';
import helpers from 'ember-htmlbars/helpers';
import validateType from 'ember-application/utils/validate-type';

export var Resolver = EmberObject.extend({
/**
Expand Down Expand Up @@ -172,6 +173,10 @@ export default EmberObject.extend({
this._logLookup(resolved, parsedName);
}

if (resolved) {
validateType(resolved, parsedName);
}

return resolved;
},

Expand Down
25 changes: 25 additions & 0 deletions packages/ember-application/lib/utils/validate-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
@module ember
@submodule ember-application
*/

let VALIDATED_TYPES = {
route: ['isRouteFactory', 'Ember.Route'],
component: ['isComponentFactory', 'Ember.Component'],
view: ['isViewFactory', 'Ember.View'],
service: ['isServiceFactory', 'Ember.Service']
};

export default function validateType(resolvedType, parsedName) {
let validationAttributes = VALIDATED_TYPES[parsedName.type];

if (!validationAttributes) {
return;
}

let [factoryFlag, expectedType] = validationAttributes;

Ember.assert(`Expected ${parsedName.fullName} to resolve to an ${expectedType} but instead it was ${resolvedType}.`, function() {
return resolvedType[factoryFlag];
});
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import Ember from "ember-metal/core"; // Ember.TEMPLATES
import run from "ember-metal/run_loop";
import { forEach } from "ember-metal/enumerable_utils";
import { capitalize } from "ember-runtime/system/string";
import Logger from "ember-metal/logger";
import Controller from "ember-runtime/controllers/controller";
import Route from "ember-routing/system/route";
import Component from "ember-views/views/component";
import View from "ember-views/views/view";
import Service from "ember-runtime/system/service";
import EmberObject from "ember-runtime/system/object";
import Namespace from "ember-runtime/system/namespace";
import Application from "ember-application/system/application";
Expand Down Expand Up @@ -170,3 +176,34 @@ QUnit.test("lookup description", function() {
equal(registry.describe('model:foo'), 'App.Foo', "models don't get appended at the end");

});

QUnit.test("validating resolved objects", function() {
let types = ['route', 'component', 'view', 'service'];

// Valid setup
application.FooRoute = Route.extend();
application.FooComponent = Component.extend();
application.FooView = View.extend();
application.FooService = Service.extend();

forEach(types, function(type) {
// No errors when resolving correct object types
registry.resolve(`${type}:foo`);

// Unregister to clear cache
registry.unregister(`${type}:foo`);
});

// Invalid setup
application.FooRoute = Component.extend();
application.FooComponent = View.extend();
application.FooView = Service.extend();
application.FooService = Route.extend();

forEach(types, function(type) {
let matcher = new RegExp(`to resolve to an Ember.${capitalize(type)}`);
expectAssertion(function() {
registry.resolve(`${type}:foo`);
}, matcher, `Should assert for ${type}`);
});
});
4 changes: 4 additions & 0 deletions packages/ember-routing/lib/system/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -1931,6 +1931,10 @@ var Route = EmberObject.extend(ActionHandler, Evented, {
}
});

Route.reopenClass({
isRouteFactory: true
});

var defaultQPMeta = {
qps: [],
map: {},
Expand Down
8 changes: 7 additions & 1 deletion packages/ember-runtime/lib/system/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,10 @@ createInjectionHelper('service');
@extends Ember.Object
@since 1.10.0
*/
export default Object.extend();
const Service = Object.extend();

Service.reopenClass({
isServiceFactory: true
});

export default Service;
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ var ViewChildViewsSupport = Mixin.create({
var view;
attrs.renderer = this.renderer;

if (maybeViewClass.isViewClass) {
if (maybeViewClass.isViewFactory) {
attrs.container = this.container;

view = maybeViewClass.create(attrs);
Expand Down
4 changes: 4 additions & 0 deletions packages/ember-views/lib/views/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,4 +390,8 @@ var Component = View.extend(TargetActionSupport, ComponentTemplateDeprecation, {
*/
});

Component.reopenClass({
isComponentFactory: true
});

export default Component;
2 changes: 1 addition & 1 deletion packages/ember-views/lib/views/core_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ var CoreView = EmberObject.extend(Evented, ActionHandler, {
});

CoreView.reopenClass({
isViewClass: true
isViewFactory: true
});

export var DeprecatedCoreView = CoreView.extend({
Expand Down

0 comments on commit a876722

Please sign in to comment.