diff --git a/packages/@ember/-internals/glimmer/lib/helpers/unique-id.ts b/packages/@ember/-internals/glimmer/lib/helpers/unique-id.ts index 032cd66b6a8..6320c24a3db 100644 --- a/packages/@ember/-internals/glimmer/lib/helpers/unique-id.ts +++ b/packages/@ember/-internals/glimmer/lib/helpers/unique-id.ts @@ -38,12 +38,14 @@ export default internalHelper((): Reference => { // This code should be reasonably fast, and provide a unique value every time // it's called, which is what we need here. It produces a string formatted as a // standard UUID, which avoids accidentally turning Ember-specific -// implementation details into an intimate API. +// implementation details into an intimate API. It also ensures that the UUID +// always starts with a letter, to avoid creating invalid IDs with a numeric +// digit at the start. function uniqueId() { // @ts-expect-error this one-liner abuses weird JavaScript semantics that // TypeScript (legitimately) doesn't like, but they're nonetheless valid and // specced. - return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (a) => - (a ^ ((Math.random() * 16) >> (a / 4))).toString(16) + return ([3e7] + -1e3 + -4e3 + -2e3 + -1e11).replace(/[0-3]/g, (a) => + ((a * 4) ^ ((Math.random() * 16) >> (a & 2))).toString(16) ); } diff --git a/packages/@ember/-internals/glimmer/tests/integration/helpers/unique-id-test.js b/packages/@ember/-internals/glimmer/tests/integration/helpers/unique-id-test.js index 763722f49ed..e4f4b2b5942 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/helpers/unique-id-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/helpers/unique-id-test.js @@ -102,6 +102,24 @@ if (EMBER_UNIQUE_ID_HELPER) { }); } + ['@test it only generates valid selectors']() { + let iterations = 1000; + let reNumericStart = /^\d/; + + let template = '

{{unique-id}}

'.repeat(iterations); + super.render(template); + + for (let i = 0; i < iterations; i++) { + let textNode = this.nthChild(i).firstChild; + let text = textNode.data; + + this.assert.false( + reNumericStart.test(text), + `{{unique-id}} should produce valid selectors` + text + ); + } + } + render(template, ...rest) { // If there are three parameters to `render`, the second parameter is the // template's arguments.