Skip to content

Commit

Permalink
Merge pull request #13177 from Serabe/fix/13061
Browse files Browse the repository at this point in the history
[BUGFIX release] Add mut to attributes in contextual components
  • Loading branch information
rwjblue committed Mar 26, 2016
2 parents a62c820 + 31bb7a2 commit b79856c
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 0 deletions.
154 changes: 154 additions & 0 deletions packages/ember-htmlbars/tests/helpers/closure_component_test.js
Original file line number Diff line number Diff line change
@@ -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 EventDispatcher from 'ember-views/system/event_dispatcher';
import compile from 'ember-template-compiler/system/compile';
import run from 'ember-metal/run_loop';
import isEmpty from 'ember-metal/is_empty';
Expand Down Expand Up @@ -614,3 +615,156 @@ QUnit.test('adding parameters to a closure component\'s instance does not add it
runAppend(component);
equal(component.$().text(), 'Foo', 'there is only one Foo');
});

QUnit.test('parameters in a closure are mutable when closure is a param', function(assert) {
let dispatcher = EventDispatcher.create();
dispatcher.setup();

let ChangeButton = Component.extend().reopenClass({
positionalParams: ['val']
});

owner.register(
'component:change-button',
ChangeButton
);
owner.register(
'template:components/change-button',
compile('<button {{action (action (mut val) 10)}} class="my-button">Change to 10</button>')
);

let template = compile('{{component (component "change-button" val2)}}<span class="value">{{val2}}</span>');

component = Component.extend({
[OWNER]: owner,
template
}).create({
val2: 8
});

runAppend(component);

assert.equal(component.$('.value').text(), '8', 'initial state is right');

run(() => component.$('.my-button').click());

assert.equal(component.$('.value').text(), '10', 'Value gets updated');

runDestroy(dispatcher);
});

QUnit.test('parameters in a closure are mutable when closure is in a nested param', function(assert) {
let dispatcher = EventDispatcher.create();
dispatcher.setup();

let ChangeButton = Component.extend().reopenClass({
positionalParams: ['val']
});

owner.register(
'component:change-button',
ChangeButton
);
owner.register(
'template:components/change-button',
compile('<button {{action (action (mut val) 10)}} class="my-button">Change to 10</button>')
);

owner.register(
'component:my-comp',
Component.extend().reopenClass({
positionalParams: ['components']
})
);
owner.register(
'template:components/my-comp',
compile('{{component components.comp}}')
);

let template = compile('{{my-comp (hash comp=(component "change-button" val2))}}<span class="value">{{val2}}</span>');

component = Component.extend({
[OWNER]: owner,
template
}).create({
val2: 8
});

runAppend(component);

assert.equal(component.$('.value').text(), '8', 'initial state is right');

run(() => component.$('.my-button').click());

assert.equal(component.$('.value').text(), '10', 'Value gets updated');

runDestroy(dispatcher);
});

QUnit.test('parameters in a closure are mutable when closure is a hash value', function(assert) {
let dispatcher = EventDispatcher.create();
dispatcher.setup();

owner.register(
'template:components/change-button',
compile('<button {{action (action (mut val) 10)}} class="my-button">Change to 10</button>')
);

owner.register(
'template:components/my-comp',
compile('{{component component}}')
);

let template = compile('{{my-comp component=(component "change-button" val=val2)}}<span class="value">{{val2}}</span>');

component = Component.extend({
[OWNER]: owner,
template
}).create({
val2: 8
});

runAppend(component);

assert.equal(component.$('.value').text(), '8', 'initial state is right');

run(() => component.$('.my-button').click());

assert.equal(component.$('.value').text(), '10', 'Value gets updated');

runDestroy(dispatcher);
});

QUnit.test('parameters in a closure are mutable when closure is a nested hash value', function(assert) {
let dispatcher = EventDispatcher.create();
dispatcher.setup();

owner.register(
'template:components/change-button',
compile('<button {{action (action (mut val) 10)}} class="my-button">Change to 10</button>')
);

owner.register(
'template:components/my-comp',
compile('{{component components.button}}')
);

let template = compile('{{my-comp components=(hash button=(component "change-button" val=val2))}}<span class="value">{{val2}}</span>');

component = Component.extend({
[OWNER]: owner,
template
}).create({
val2: 8
});

runAppend(component);

assert.equal(component.$('.value').text(), '8', 'initial state is right');

run(() => component.$('.my-button').click());

assert.equal(component.$('.value').text(), '10', 'Value gets updated');

runDestroy(dispatcher);
});
2 changes: 2 additions & 0 deletions packages/ember-template-compiler/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { registerPlugin } from 'ember-template-compiler/plugins';
import TransformOldBindingSyntax from 'ember-template-compiler/plugins/transform-old-binding-syntax';
import TransformOldClassBindingSyntax from 'ember-template-compiler/plugins/transform-old-class-binding-syntax';
import TransformItemClass from 'ember-template-compiler/plugins/transform-item-class';
import TransformClosureComponentAttrsIntoMut from 'ember-template-compiler/plugins/transform-closure-component-attrs-into-mut';
import TransformComponentAttrsIntoMut from 'ember-template-compiler/plugins/transform-component-attrs-into-mut';
import TransformComponentCurlyToReadonly from 'ember-template-compiler/plugins/transform-component-curly-to-readonly';
import TransformAngleBracketComponents from 'ember-template-compiler/plugins/transform-angle-bracket-components';
Expand All @@ -23,6 +24,7 @@ import 'ember-template-compiler/compat';
registerPlugin('ast', TransformOldBindingSyntax);
registerPlugin('ast', TransformOldClassBindingSyntax);
registerPlugin('ast', TransformItemClass);
registerPlugin('ast', TransformClosureComponentAttrsIntoMut);
registerPlugin('ast', TransformComponentAttrsIntoMut);
registerPlugin('ast', TransformComponentCurlyToReadonly);
registerPlugin('ast', TransformAngleBracketComponents);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
function TransformClosureComponentAttrsIntoMut() {
// set later within HTMLBars to the syntax package
this.syntax = null;
}

/**
@private
@method transform
@param {AST} ast The AST to be transformed.
*/
TransformClosureComponentAttrsIntoMut.prototype.transform = function TransformClosureComponentAttrsIntoMut_transform(ast) {
let b = this.syntax.builders;
let walker = new this.syntax.Walker();

walker.visit(ast, function(node) {
if (validate(node)) {
processExpression(b, node);
}
});

return ast;
};

function processExpression(builder, node) {
processSubExpressionsInNode(builder, node);

if (isComponentClosure(node)) {
mutParameters(builder, node);
}
}

function processSubExpressionsInNode(builder, node) {
for (let i = 0; i < node.params.length; i++) {
if (node.params[i].type === 'SubExpression') {
processExpression(builder, node.params[i]);
}
}

each(node.hash.pairs, function(pair) {
let { value } = pair;

if (value.type === 'SubExpression') {
processExpression(builder, value);
}
});
}

function isComponentClosure(node) {
return node.type === 'SubExpression' && node.path.original === 'component';
}

function mutParameters(builder, node) {
for (let i = 1; i < node.params.length; i++) {
if (node.params[i].type === 'PathExpression') {
node.params[i] = builder.sexpr(builder.path('@mut'), [node.params[i]]);
}
}

each(node.hash.pairs, function(pair) {
let { value } = pair;

if (value.type === 'PathExpression') {
pair.value = builder.sexpr(builder.path('@mut'), [pair.value]);
}
});
}

function validate(node) {
return node.type === 'BlockStatement' || node.type === 'MustacheStatement';
}

function each(list, callback) {
for (var i = 0, l = list.length; i < l; i++) {
callback(list[i]);
}
}

export default TransformClosureComponentAttrsIntoMut;

0 comments on commit b79856c

Please sign in to comment.