Skip to content

Commit

Permalink
Merge pull request #19977 from Windvis/unique-id
Browse files Browse the repository at this point in the history
Add a feature flag for the unique-id helper
  • Loading branch information
locks authored Feb 18, 2022
2 parents b8c78bf + fabfc8b commit 79186c3
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 111 deletions.
6 changes: 5 additions & 1 deletion packages/@ember/-internals/glimmer/lib/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { privatize as P } from '@ember/-internals/container';
import { ENV } from '@ember/-internals/environment';
import { Factory, FactoryClass, LookupOptions, Owner } from '@ember/-internals/owner';
import { EMBER_UNIQUE_ID_HELPER } from '@ember/canary-features';
import { assert } from '@ember/debug';
import { _instrumentStart } from '@ember/instrumentation';
import { DEBUG } from '@glimmer/env';
Expand Down Expand Up @@ -147,9 +148,12 @@ const BUILTIN_HELPERS = {
fn,
get,
hash,
'unique-id': uniqueId,
};

if (EMBER_UNIQUE_ID_HELPER) {
BUILTIN_HELPERS['unique-id'] = uniqueId;
}

const BUILTIN_KEYWORD_MODIFIERS = {
action: actionModifier,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,144 +1,147 @@
import { RenderingTestCase, strip, moduleFor, runTask } from 'internal-test-helpers';
import { setProperties } from '@ember/-internals/metal';

moduleFor(
'Helpers test: {{unique-id}}',
class extends RenderingTestCase {
['@test it generates a unique id (string) each time']() {
let { first, second } = this.render(`<p>{{unique-id}}</p><p>{{unique-id}}</p>`, () => {
let first = this.asElement(this.firstChild);
let second = this.asElement(this.nthChild(1));

return {
first: this.asTextContent(first.firstChild),
second: this.asTextContent(second.firstChild),
};
});

this.assert.notStrictEqual(
first,
second,
`different invocations of {{unique-id}} should produce different values`
);
}

[`@test when unique-id is used with #let, it remains stable when it's used`]() {
let { first, second } = this.render(
strip`
{{#let (unique-id) as |id|}}
<p>{{id}}</p><p>{{id}}</p>
{{/let}}
`,
() => {
import { EMBER_UNIQUE_ID_HELPER } from '@ember/canary-features';

if (EMBER_UNIQUE_ID_HELPER) {
moduleFor(
'Helpers test: {{unique-id}}',
class extends RenderingTestCase {
['@test it generates a unique id (string) each time']() {
let { first, second } = this.render(`<p>{{unique-id}}</p><p>{{unique-id}}</p>`, () => {
let first = this.asElement(this.firstChild);
let second = this.asElement(this.nthChild(1));

return {
first: this.asTextContent(first.firstChild),
second: this.asTextContent(second.firstChild),
};
}
);
});

this.assert.strictEqual(
first,
second,
`when unique-id is used as a variable, it remains the same`
);
}
this.assert.notStrictEqual(
first,
second,
`different invocations of {{unique-id}} should produce different values`
);
}

[`@test unique-id doesn't change if it's concatenated with a value that does change`]() {
class Elements {
constructor(label, input, assert) {
this.label = label;
this.input = input;
this.assert = assert;
}
[`@test when unique-id is used with #let, it remains stable when it's used`]() {
let { first, second } = this.render(
strip`
{{#let (unique-id) as |id|}}
<p>{{id}}</p><p>{{id}}</p>
{{/let}}
`,
() => {
let first = this.asElement(this.firstChild);
let second = this.asElement(this.nthChild(1));

return {
first: this.asTextContent(first.firstChild),
second: this.asTextContent(second.firstChild),
};
}
);

id(regex) {
let forAttr = this.label.getAttribute('for');
this.assert.strictEqual(
first,
second,
`when unique-id is used as a variable, it remains the same`
);
}

this.assert.strictEqual(
forAttr,
this.input.getAttribute('id'),
`the label's 'for' attribute should be the same as the input's 'id' attribute`
);
[`@test unique-id doesn't change if it's concatenated with a value that does change`]() {
class Elements {
constructor(label, input, assert) {
this.label = label;
this.input = input;
this.assert = assert;
}

let match = forAttr.match(regex);
id(regex) {
let forAttr = this.label.getAttribute('for');

this.assert.ok(match, 'the id starts with the prefix');
this.assert.strictEqual(
forAttr,
this.input.getAttribute('id'),
`the label's 'for' attribute should be the same as the input's 'id' attribute`
);

return match[1];
let match = forAttr.match(regex);

this.assert.ok(match, 'the id starts with the prefix');

return match[1];
}
}
}

let { elements, id } = this.render(
strip`
let { elements, id } = this.render(
strip`
{{#let (unique-id) as |id|}}
<label for="{{this.prefix}}-{{id}}">Enable Feature</label>
<input id="{{this.prefix}}-{{id}}" type="checkbox">
{{/let}}`,
{ prefix: 'app' },
() => {
let label = this.asElement(this.firstChild, 'label');
let input = this.asElement(this.nthChild(1), 'input');

let elements = new Elements(label, input, this.assert);

return { elements, id: elements.id(/^app-(.*)$/) };
}
);
{ prefix: 'app' },
() => {
let label = this.asElement(this.firstChild, 'label');
let input = this.asElement(this.nthChild(1), 'input');

this.update({ prefix: 'melanie' }, () => {
let newId = elements.id(/^melanie-(.*)$/);
let elements = new Elements(label, input, this.assert);

this.assert.strictEqual(
id,
newId,
`the unique-id part of a concatenated attribute shouldn't change just because a dynamic part of it changed`
return { elements, id: elements.id(/^app-(.*)$/) };
}
);
});
}

render(template, ...rest) {
// If there are three parameters to `render`, the second parameter is the
// template's arguments.
let args = rest.length === 2 ? rest[0] : {};
// If there are two parameters to `render`, the second parameter is the
// postcondition. Otherwise, the third parameter is the postcondition.
let postcondition = rest.length === 2 ? rest[1] : rest[0];

super.render(template, args);
let result = postcondition();
this.assertStableRerender();
return result;
}
this.update({ prefix: 'melanie' }, () => {
let newId = elements.id(/^melanie-(.*)$/);

update(args, postcondition) {
runTask(() => setProperties(this.context, args));
postcondition();
this.assertStableRerender();
}
this.assert.strictEqual(
id,
newId,
`the unique-id part of a concatenated attribute shouldn't change just because a dynamic part of it changed`
);
});
}

asElement(node, tag) {
this.assert.ok(node !== null && node.nodeType === 1);
render(template, ...rest) {
// If there are three parameters to `render`, the second parameter is the
// template's arguments.
let args = rest.length === 2 ? rest[0] : {};
// If there are two parameters to `render`, the second parameter is the
// postcondition. Otherwise, the third parameter is the postcondition.
let postcondition = rest.length === 2 ? rest[1] : rest[0];

super.render(template, args);
let result = postcondition();
this.assertStableRerender();
return result;
}

if (tag) {
this.assert.strictEqual(node.tagName.toLowerCase(), tag, `Element is <${tag}>`);
update(args, postcondition) {
runTask(() => setProperties(this.context, args));
postcondition();
this.assertStableRerender();
}

return node;
}
asElement(node, tag) {
this.assert.ok(node !== null && node.nodeType === 1);

asTextNode(node) {
this.assert.ok(node !== null && node.nodeType === 3);
return node;
}
if (tag) {
this.assert.strictEqual(node.tagName.toLowerCase(), tag, `Element is <${tag}>`);
}

asTextContent(node) {
let data = this.asTextNode(node).data;
this.assert.ok(data.trim().length > 0, `The text node has content`);
return data;
return node;
}

asTextNode(node) {
this.assert.ok(node !== null && node.nodeType === 3);
return node;
}

asTextContent(node) {
let data = this.asTextNode(node).data;
this.assert.ok(data.trim().length > 0, `The text node has content`);
return data;
}
}
}
);
);
}
2 changes: 2 additions & 0 deletions packages/@ember/canary-features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const DEFAULT_FEATURES = {
EMBER_DYNAMIC_HELPERS_AND_MODIFIERS: true,
EMBER_ROUTING_ROUTER_SERVICE_REFRESH: true,
EMBER_CACHED: true,
EMBER_UNIQUE_ID_HELPER: null,
};

/**
Expand Down Expand Up @@ -82,3 +83,4 @@ export const EMBER_ROUTING_ROUTER_SERVICE_REFRESH = featureValue(
FEATURES.EMBER_ROUTING_ROUTER_SERVICE_REFRESH
);
export const EMBER_CACHED = featureValue(FEATURES.EMBER_CACHED);
export const EMBER_UNIQUE_ID_HELPER = featureValue(FEATURES.EMBER_UNIQUE_ID_HELPER);

0 comments on commit 79186c3

Please sign in to comment.