Skip to content

Commit

Permalink
[RFC emberjs#435] Add feature flag and tests for forwarding modifiers…
Browse files Browse the repository at this point in the history
… with splattributes
  • Loading branch information
cibernox committed Apr 2, 2019
1 parent a5dcda4 commit 4c7b3a0
Show file tree
Hide file tree
Showing 9 changed files with 512 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
- if: branch = master
env:
- BUILD_TYPE=alpha
- OVERRIDE_FEATURES=EMBER_METAL_TRACKED_PROPERTIES,EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS,EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP,EMBER_NATIVE_DECORATOR_SUPPORT
- OVERRIDE_FEATURES=EMBER_METAL_TRACKED_PROPERTIES,EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS,EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP,EMBER_NATIVE_DECORATOR_SUPPORT,EMBER_GLIMMER_PASS_MODIFIERS_WITH_SPLATTRIBUTES
- PUBLISH=true
script:
- "./bin/publish_builds"
7 changes: 7 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,10 @@ for a detailed explanation.
with the angle bracket invocation sytnax.

See [RFC #459](https://github.com/emberjs/rfcs/pull/459).

* `ember-glimmer-pass-modifiers-with-splattributes`

Allows element modifiers to be applied to components that use angle-bracket syntax, and applies
those modifiers to the element or elements receiving the splattributes.

See [RFC #435](https://github.com/emberjs/rfcs/pull/435).
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@
"@babel/plugin-transform-shorthand-properties": "^7.2.0",
"@babel/plugin-transform-spread": "^7.2.2",
"@babel/plugin-transform-template-literals": "^7.2.0",
"@glimmer/compiler": "^0.38.1",
"@glimmer/compiler": "0.38.2-alpha.2",
"@glimmer/env": "^0.1.7",
"@glimmer/interfaces": "^0.38.1",
"@glimmer/node": "^0.38.1",
"@glimmer/opcode-compiler": "^0.38.1",
"@glimmer/program": "^0.38.1",
"@glimmer/reference": "^0.38.1",
"@glimmer/runtime": "^0.38.1",
"@glimmer/interfaces": "0.38.2-alpha.2",
"@glimmer/node": "0.38.2-alpha.2",
"@glimmer/opcode-compiler": "0.38.2-alpha.2",
"@glimmer/program": "0.38.2-alpha.2",
"@glimmer/reference": "0.38.2-alpha.2",
"@glimmer/runtime": "0.38.2-alpha.2",
"@types/qunit": "^2.5.4",
"@types/rsvp": "^4.0.2",
"auto-dist-tag": "^1.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,50 @@
import { moduleFor, RenderingTestCase, strip, classes, runTask } from 'internal-test-helpers';
import { setModifierManager } from '@ember/-internals/glimmer';
import { Object as EmberObject } from '@ember/-internals/runtime';

import { EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP } from '@ember/canary-features';
import { set } from '@ember/-internals/metal';
import {
EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP,
EMBER_GLIMMER_PASS_MODIFIERS_WITH_SPLATTRIBUTES,
} from '@ember/canary-features';
import { set, setProperties } from '@ember/-internals/metal';

import { Component } from '../../utils/helpers';

class CustomModifierManager {
constructor(owner) {
this.owner = owner;
}

createModifier(factory, args) {
return factory.create(args);
}

installModifier(instance, element, args) {
instance.element = element;
let { positional, named } = args;
instance.didInsertElement(positional, named);
}

updateModifier(instance, args) {
let { positional, named } = args;
instance.didUpdate(positional, named);
}

destroyModifier(instance) {
instance.willDestroyElement();
}
}
let BaseModifier = setModifierManager(
owner => {
return new CustomModifierManager(owner);
},
EmberObject.extend({
didInsertElement() {},
didUpdate() {},
willDestroyElement() {},
})
);

moduleFor(
'AngleBracket Invocation',
class extends RenderingTestCase {
Expand Down Expand Up @@ -1064,3 +1104,246 @@ if (EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP) {
}
);
}

if (EMBER_GLIMMER_PASS_MODIFIERS_WITH_SPLATTRIBUTES) {
moduleFor(
'Element modifiers on AngleBracket components',
class extends RenderingTestCase {
'@test modifiers are forwarded to a single element receiving the splattributes'(assert) {
let modifierParams = null;
let modifierNamedArgs = null;
let modifiedElement;
this.registerComponent('the-foo', {
ComponentClass: Component.extend({ tagName: '' }),
template: '<div id="inner-div" ...attributes>Foo</div>',
});
this.registerModifier(
'bar',
BaseModifier.extend({
didInsertElement(params, namedArgs) {
modifierParams = params;
modifierNamedArgs = namedArgs;
modifiedElement = this.element;
},
})
);
this.render('<TheFoo {{bar "something" foo="else"}}/>', {});
assert.deepEqual(modifierParams, ['something']);
assert.deepEqual(modifierNamedArgs, { foo: 'else' });
assert.equal(
modifiedElement && modifiedElement.getAttribute('id'),
'inner-div',
'Modifier is called on the element receiving the splattributes'
);
}

'@test modifiers are forwarded to all the elements receiving the splattributes'(assert) {
let elementIds = [];
this.registerComponent('the-foo', {
ComponentClass: Component.extend({ tagName: '' }),
template:
'<div id="inner-one" ...attributes>Foo</div><div id="inner-two" ...attributes>Bar</div>',
});
this.registerModifier(
'bar',
BaseModifier.extend({
didInsertElement(params, namedArgs) {
assert.deepEqual(params, ['something']);
assert.deepEqual(namedArgs, { foo: 'else' });
if (this.element) {
elementIds.push(this.element.getAttribute('id'));
}
},
})
);
this.render('<TheFoo {{bar "something" foo="else"}}/>');
assert.deepEqual(
elementIds,
['inner-one', 'inner-two'],
'The modifier has been instantiated twice, once for each element with splattributes'
);
}

'@test modifiers on components accept bound arguments and track changes on the'(assert) {
let modifierParams = null;
let modifierNamedArgs = null;
let modifiedElement;
this.registerComponent('the-foo', {
ComponentClass: Component.extend({ tagName: '' }),
template: '<div id="inner-div" ...attributes>Foo</div>',
});
this.registerModifier(
'bar',
BaseModifier.extend({
didInsertElement(params, namedArgs) {
modifierParams = params;
modifierNamedArgs = namedArgs;
modifiedElement = this.element;
},
didUpdate(params, namedArgs) {
modifierParams = params;
modifierNamedArgs = namedArgs;
modifiedElement = this.element;
},
})
);
this.render('<TheFoo {{bar this.something foo=this.foo}}/>', {
something: 'something',
foo: 'else',
});
assert.deepEqual(modifierParams, ['something']);
assert.deepEqual(modifierNamedArgs, { foo: 'else' });
assert.equal(
modifiedElement && modifiedElement.getAttribute('id'),
'inner-div',
'Modifier is called on the element receiving the splattributes'
);
runTask(() => setProperties(this.context, { something: 'another', foo: 'thingy' }));
assert.deepEqual(modifierParams, ['another']);
assert.deepEqual(modifierNamedArgs, { foo: 'thingy' });
assert.equal(
modifiedElement && modifiedElement.getAttribute('id'),
'inner-div',
'Modifier is called on the element receiving the splattributes'
);
}

'@test modifiers on components accept `this` in both positional params and named arguments, and updates when it changes'(
assert
) {
let modifierParams = null;
let modifierNamedArgs = null;
let modifiedElement;
let context = { id: 1 };
let context2 = { id: 2 };
this.registerComponent('the-foo', {
ComponentClass: Component.extend({ tagName: '' }),
template: '<div id="inner-div" ...attributes>Foo</div>',
});
this.registerModifier(
'bar',
BaseModifier.extend({
didInsertElement(params, namedArgs) {
modifierParams = params;
modifierNamedArgs = namedArgs;
modifiedElement = this.element;
},
didUpdate(params, namedArgs) {
modifierParams = params;
modifierNamedArgs = namedArgs;
modifiedElement = this.element;
},
})
);
this.render('<TheFoo {{bar "name" this foo=this}}/>', context);
assert.equal(modifierParams[1].id, 1);
assert.equal(modifierNamedArgs.foo.id, 1);
assert.equal(
modifiedElement && modifiedElement.getAttribute('id'),
'inner-div',
'Modifier is called on the element receiving the splattributes'
);
runTask(() => setProperties(this.context, context2));
assert.equal(modifierParams[1].id, 2);
assert.equal(modifierNamedArgs.foo.id, 2);
assert.equal(
modifiedElement && modifiedElement.getAttribute('id'),
'inner-div',
'Modifier is called on the element receiving the splattributes'
);
}

'@test modifiers on components accept local variables in both positional params and named arguments, and updates when they change'(
assert
) {
let modifierParams = null;
let modifierNamedArgs = null;
let modifiedElement;
this.registerComponent('the-foo', {
ComponentClass: Component.extend({ tagName: '' }),
template: '<div id="inner-div" ...attributes>Foo</div>',
});
this.registerModifier(
'bar',
BaseModifier.extend({
didInsertElement(params, namedArgs) {
modifierParams = params;
modifierNamedArgs = namedArgs;
modifiedElement = this.element;
},
didUpdate(params, namedArgs) {
modifierParams = params;
modifierNamedArgs = namedArgs;
modifiedElement = this.element;
},
})
);
this.render(
`
{{#let this.foo as |v|}}
<TheFoo {{bar v foo=v}}/>
{{/let}}`,
{ foo: 'bar' }
);
assert.deepEqual(modifierParams, ['bar']);
assert.deepEqual(modifierNamedArgs, { foo: 'bar' });
assert.equal(
modifiedElement && modifiedElement.getAttribute('id'),
'inner-div',
'Modifier is called on the element receiving the splattributes'
);
runTask(() => setProperties(this.context, { foo: 'qux' }));
assert.deepEqual(modifierParams, ['qux']);
assert.deepEqual(modifierNamedArgs, { foo: 'qux' });
assert.equal(
modifiedElement && modifiedElement.getAttribute('id'),
'inner-div',
'Modifier is called on the element receiving the splattributes'
);
}

'@test modifiers on components can be received and forwarded to inner component'(assert) {
let modifierParams = null;
let modifierNamedArgs = null;
let elementIds = [];

this.registerComponent('the-inner', {
ComponentClass: Component.extend({ tagName: '' }),
template: '<div id="inner-div" ...attributes>{{yield}}</div>',
});
this.registerComponent('the-foo', {
ComponentClass: Component.extend({ tagName: '' }),
template:
'<div id="outer-div" ...attributes>Outer</div><TheInner ...attributes>Hello</TheInner>',
});
this.registerModifier(
'bar',
BaseModifier.extend({
didInsertElement(params, namedArgs) {
modifierParams = params;
modifierNamedArgs = namedArgs;
if (this.element) {
elementIds.push(this.element.getAttribute('id'));
}
},
})
);
this.render(
`
{{#let this.foo as |v|}}
<TheFoo {{bar v foo=v}}/>
{{/let}}
`,
{ foo: 'bar' }
);
assert.deepEqual(modifierParams, ['bar']);
assert.deepEqual(modifierNamedArgs, { foo: 'bar' });
assert.deepEqual(
elementIds,
['outer-div', 'inner-div'],
'Modifiers are called on all levels'
);
}
}
);
}
4 changes: 4 additions & 0 deletions packages/@ember/canary-features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const DEFAULT_FEATURES = {
EMBER_IMPROVED_INSTRUMENTATION: null,
EMBER_MODULE_UNIFICATION: null,
EMBER_METAL_TRACKED_PROPERTIES: null,
EMBER_GLIMMER_PASS_MODIFIERS_WITH_SPLATTRIBUTES: null,
EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS: true,
EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP: true,
EMBER_ROUTING_BUILD_ROUTEINFO_METADATA: true,
Expand Down Expand Up @@ -76,6 +77,9 @@ export const EMBER_METAL_TRACKED_PROPERTIES = featureValue(FEATURES.EMBER_METAL_
export const EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP = featureValue(
FEATURES.EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP
);
export const EMBER_GLIMMER_PASS_MODIFIERS_WITH_SPLATTRIBUTES = featureValue(
FEATURES.EMBER_GLIMMER_PASS_MODIFIERS_WITH_SPLATTRIBUTES
);
export const EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS = featureValue(
FEATURES.EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS
);
Expand Down
Loading

0 comments on commit 4c7b3a0

Please sign in to comment.