Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUGFIX canary] Deprecate passing function as test argument to deprecate/warn/assert #12370

Merged
merged 1 commit into from
Oct 1, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions packages/ember-application/lib/utils/validate-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { assert, deprecate } from 'ember-metal/debug';

let VALIDATED_TYPES = {
const VALIDATED_TYPES = {
route: ['assert', 'isRouteFactory', 'Ember.Route'],
component: ['deprecate', 'isComponentFactory', 'Ember.Component'],
view: ['deprecate', 'isViewFactory', 'Ember.View'],
Expand All @@ -27,16 +27,14 @@ export default function validateType(resolvedType, parsedName) {
`property set to true. You registered ${resolvedType} as a ${parsedName.type} ` +
`factory. Either add the \`${factoryFlag}\` property to this factory or ` +
`extend from ${expectedType}.`,
resolvedType[factoryFlag],
!!resolvedType[factoryFlag],
{ id: 'ember-application.validate-type', until: '3.0.0' }
);
} else {
assert(
`Expected ${parsedName.fullName} to resolve to an ${expectedType} but ` +
`instead it was ${resolvedType}.`,
function() {
return resolvedType[factoryFlag];
}
!!resolvedType[factoryFlag]
);
}
}
5 changes: 2 additions & 3 deletions packages/ember-debug/lib/deprecate.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,8 @@ export let missingOptionsUntilDeprecation = 'When calling `Ember.deprecate` you

@method deprecate
@param {String} message A description of the deprecation.
@param {Boolean|Function} test A boolean. If falsy, the deprecation
will be displayed. If this is a function, it will be executed and its return
value will be used as condition.
@param {Boolean} test A boolean. If falsy, the deprecation
will be displayed.
@param {Object} options An object that can be used to pass
in a `url` to the transition guide on the emberjs.com website, and a unique
`id` for this deprecation. The `id` can be used by Ember debugging tools
Expand Down
23 changes: 20 additions & 3 deletions packages/ember-debug/lib/handlers.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import isPlainFunction from 'ember-debug/is-plain-function';
import deprecate from 'ember-debug/deprecate';

export let HANDLERS = { };

function normalizeTest(test) {
return isPlainFunction(test) ? test() : test;
export function generateTestAsFunctionDeprecation(source) {
return `Calling \`${source}\` with a function argument is deprecated. Please ` +
`use \`!!Constructor\` for constructors, or an \`IIFE\` to compute the test for deprecation. ` +
`In a future version functions will be treated as truthy values instead of being executed.`;
}

function normalizeTest(test, source) {
if (isPlainFunction(test)) {
deprecate(
generateTestAsFunctionDeprecation(source),
false,
{ id: 'ember-debug.deprecate-test-as-function', until: '2.5.0' }
);

return test();
}

return test;
}

export function registerHandler(type, callback) {
Expand All @@ -15,7 +32,7 @@ export function registerHandler(type, callback) {
}

export function invoke(type, message, test, options) {
if (normalizeTest(test)) { return; }
if (normalizeTest(test, 'Ember.' + type)) { return; }

let handlerForType = HANDLERS[type];

Expand Down
14 changes: 10 additions & 4 deletions packages/ember-debug/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import _warn, {
registerHandler as registerWarnHandler
} from 'ember-debug/warn';
import isPlainFunction from 'ember-debug/is-plain-function';
import { generateTestAsFunctionDeprecation } from 'ember-debug/handlers';

/**
@module ember
Expand Down Expand Up @@ -44,15 +45,20 @@ import isPlainFunction from 'ember-debug/is-plain-function';
@method assert
@param {String} desc A description of the assertion. This will become
the text of the Error thrown if the assertion fails.
@param {Boolean|Function} test Must be truthy for the assertion to pass. If
falsy, an exception will be thrown. If this is a function, it will be executed and
its return value will be used as condition.
@param {Boolean} test Must be truthy for the assertion to pass. If
falsy, an exception will be thrown.
@public
*/
setDebugFunction('assert', function assert(desc, test) {
var throwAssertion;
let throwAssertion;

if (isPlainFunction(test)) {
deprecate(
generateTestAsFunctionDeprecation('Ember.assert'),
false,
{ id: 'ember-debug.deprecate-test-as-function', until: '2.5.0' }
);

throwAssertion = !test();
} else {
throwAssertion = !test;
Expand Down
69 changes: 51 additions & 18 deletions packages/ember-debug/tests/main_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Ember from 'ember-metal/core';
import EmberObject from 'ember-runtime/system/object';
import { HANDLERS } from 'ember-debug/handlers';
import { HANDLERS, generateTestAsFunctionDeprecation } from 'ember-debug/handlers';
import {
registerHandler,
missingOptionsDeprecation,
Expand All @@ -13,6 +13,7 @@ import {
missingOptionsDeprecation as missingWarnOptionsDeprecation,
registerHandler as registerWarnHandler
} from 'ember-debug/warn';
import deprecate from 'ember-debug/deprecate';

let originalEnvValue;
let originalDeprecateHandler;
Expand Down Expand Up @@ -106,14 +107,12 @@ QUnit.test('Ember.deprecate throws deprecation if second argument is falsy', fun
});
});

QUnit.test('Ember.deprecate does not throw deprecation if second argument is a function and it returns true', function() {
expect(1);

Ember.deprecate('Deprecation is thrown', function() {
return true;
}, { id: 'test', until: 'forever' });
QUnit.test('Ember.deprecate throws deprecation if second argument is a function and it returns true', function(assert) {
assert.expect(1);

ok(true, 'deprecation was not thrown');
throws(() => {
Ember.deprecate('This deprecation is not thrown, but argument deprecation is thrown', () => true, { id: 'test', until: 'forever' });
});
});

QUnit.test('Ember.deprecate throws if second argument is a function and it returns false', function() {
Expand Down Expand Up @@ -151,14 +150,14 @@ QUnit.test('Ember.assert throws if second argument is falsy', function() {
});
});

QUnit.test('Ember.assert does not throw if second argument is a function and it returns true', function() {
expect(1);

Ember.assert('Assertion is thrown', function() {
return true;
});
QUnit.test('Ember.assert does not throw if second argument is a function and it returns true', function(assert) {
assert.expect(1);

ok(true, 'assertion was not thrown');
// shouldn't trigger an assertion, but deprecation from using function as test is expected
expectDeprecation(
() => Ember.assert('Assertion is thrown', () => true),
generateTestAsFunctionDeprecation('Ember.assert')
);
});

QUnit.test('Ember.assert throws if second argument is a function and it returns false', function() {
Expand All @@ -182,7 +181,7 @@ QUnit.test('Ember.assert does not throw if second argument is truthy', function(

QUnit.test('Ember.assert does not throw if second argument is an object', function() {
expect(1);
var Igor = EmberObject.extend();
let Igor = EmberObject.extend();

Ember.assert('is truthy', Igor);
Ember.assert('is truthy', Igor.create());
Expand Down Expand Up @@ -224,8 +223,6 @@ QUnit.test('Ember.deprecate does not throw a deprecation at log and silence leve
Ember.deprecate('Deprecation is thrown', false, { id, until });
});



throws(function() {
Ember.deprecate('Deprecation is thrown', false, { id, until });
});
Expand Down Expand Up @@ -301,3 +298,39 @@ QUnit.test('Ember.warn without options.id triggers a deprecation', function(asse

Ember.warn('foo', false, { });
});

QUnit.test('Ember.deprecate triggers a deprecation when test argument is a function', function(assert) {
assert.expect(1);

registerHandler(message => assert.equal(
message,
generateTestAsFunctionDeprecation('Ember.deprecate'),
'proper deprecation is triggered when test argument is a function'
));

deprecate('Deprecation is thrown', () => true, { id: 'test', until: 'forever' });
});

QUnit.test('Ember.warn triggers a deprecation when test argument is a function', function(assert) {
assert.expect(1);

registerHandler(message => assert.equal(
message,
generateTestAsFunctionDeprecation('Ember.warn'),
'proper deprecation is triggered when test argument is a function'
));

Ember.warn('Warning is thrown', () => true, { id: 'test' });
});

QUnit.test('Ember.assert triggers a deprecation when test argument is a function', function(assert) {
assert.expect(1);

registerHandler(message => assert.equal(
message,
generateTestAsFunctionDeprecation('Ember.assert'),
'proper deprecation is triggered when test argument is a function'
));

Ember.assert('Assertion is thrown', () => true);
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ function ViewNodeManager(component, scope, renderNode, block, expectElement) {
export default ViewNodeManager;

ViewNodeManager.create = function ViewNodeManager_create(renderNode, env, attrs, found, parentView, path, contentScope, contentTemplate) {
assert('HTMLBars error: Could not find component named "' + path + '" (no component or template with that name was found)', function() {
assert('HTMLBars error: Could not find component named "' + path + '" (no component or template with that name was found)', !!(function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of rewriting to avoid the function, can you make the function an IIFE instead? I personally find the function style of writing this much easier to understand.

if (path) {
return found.component || found.layout;
} else {
return found.component || found.layout || contentTemplate;
}
});
}()));

var component;
var componentInfo = { layout: found.layout };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import ViewNodeManager from 'ember-htmlbars/node-managers/view-node-manager';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THANK YOU FOR WRITING TESTS FOR THIS!!!!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:P Pssst. There's component-node-manager too.

I was thinking this should be pulled out to another PR so it doesn't interfere with the [BUGFIX release]. I guess it doesn't need to go into release though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are to confirm the changes in the assertion work properly (that is why there isn't a corresponding change to component-node-manager.js).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I needed it to help me confirm I kept the same logic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok 👍


QUnit.module('ember-htmlbars: node-managers - ViewNodeManager');

QUnit.test('create method should assert if component hasn\'t been found', assert => {
assert.expect(1);

let found = {
component: null,
layout: null
};

let path;

expectAssertion(() => {
ViewNodeManager.create(null, null, null, found, null, path);
}, 'HTMLBars error: Could not find component named "' + path + '" (no component or template with that name was found)');
});

QUnit.test('create method shouldn\'t assert if `found.component` is truthy', assert => {
assert.expect(1);

let found = {
component: {},
layout: null
};
let attrs = {};
let renderNode = {};

let env = {
renderer: {
componentUpdateAttrs() {
assert.ok('env.renderer.componentUpdateAttrs called');
}
}
};

ViewNodeManager.create(renderNode, env, attrs, found);
});

QUnit.test('create method shouldn\'t assert if `found.layout` is truthy', assert => {
assert.expect(0);

let found = {
component: null,
layout: true
};

ViewNodeManager.create(null, null, null, found);
});

QUnit.test('create method shouldn\'t assert if `path` is falsy and `contentTemplate` is truthy', assert => {
assert.expect(0);

let found = {
component: null,
layout: null
};
let path = null;
let contentTemplate = true;

ViewNodeManager.create(null, null, null, found, null, path, null, contentTemplate);
});
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ function assertPaths(moduleName, node, paths) {

function assertPath(moduleName, node, path) {
assert(
`Using \`{{${path && path.type === 'PathExpression' && path.parts[0]}}}\` or any path based on it ${calculateLocationDisplay(moduleName, node.loc)}has been removed in Ember 2.0`,
function assertPath_test() {
`Using \`{{${path && path.type === 'PathExpression' && path.parts[0]}}}\` or any path based on it ${calculateLocationDisplay(moduleName, node.loc)}has been removed in Ember 2.0`, () => {
let noAssertion = true;

const viewKeyword = path && path.type === 'PathExpression' && path.parts && path.parts[0];
Expand All @@ -65,7 +64,7 @@ function assertPath(moduleName, node, path) {
}

return noAssertion;
}, {
}(), {
id: (path.parts && path.parts[0] === 'view' ? 'view.keyword.view' : 'view.keyword.controller'),
until: '2.0.0'
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ember-views/lib/system/build-component-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,8 @@ function normalizeClasses(classes, output, streamBasePath) {
}

function validateTaglessComponent(component) {
assert('You cannot use `classNameBindings` on a tag-less component: ' + component.toString(), function() {
assert('You cannot use `classNameBindings` on a tag-less component: ' + component.toString(), () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind tweaking this into an IIFE to keep the function style?

var classNameBindings = component.classNameBindings;
return !classNameBindings || classNameBindings.length === 0;
});
}());
}
12 changes: 6 additions & 6 deletions packages/ember-views/lib/views/container_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,16 +245,16 @@ var ContainerView = View.extend(MutableArray, {
layout: containerViewTemplate,

replace(idx, removedCount, addedViews=[]) {
var addedCount = get(addedViews, 'length');
var childViews = get(this, 'childViews');
let addedCount = get(addedViews, 'length');
let childViews = get(this, 'childViews');

assert('You can\'t add a child to a container - the child is already a child of another view', () => {
for (var i = 0, l = addedViews.length; i < l; i++) {
var item = addedViews[i];
for (let i = 0, l = addedViews.length; i < l; i++) {
let item = addedViews[i];
if (item.parentView && item.parentView !== this) { return false; }
}
return true;
});
}());

this.arrayContentWillChange(idx, removedCount, addedCount);

Expand All @@ -266,7 +266,7 @@ var ContainerView = View.extend(MutableArray, {
// Because of this, we synchronously fix up the parentView/childViews tree
// as soon as views are added or removed, despite the fact that this will
// happen automatically when we render.
var removedViews = childViews.slice(idx, idx + removedCount);
let removedViews = childViews.slice(idx, idx + removedCount);
removedViews.forEach(view => this.unlinkChild(view));
addedViews.forEach(view => this.linkChild(view));

Expand Down