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

[RFC #435] Add feature flag and tests for forwarding modifiers with splattributes #17842

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
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_FORWARD_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-forward-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_FORWARD_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_FORWARD_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_FORWARD_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_FORWARD_MODIFIERS_WITH_SPLATTRIBUTES = featureValue(
FEATURES.EMBER_GLIMMER_FORWARD_MODIFIERS_WITH_SPLATTRIBUTES
);
export const EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS = featureValue(
FEATURES.EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS
);
Expand Down
Loading