diff --git a/src/style.js b/src/style.js index d7f1ef0975..e247afece4 100644 --- a/src/style.js +++ b/src/style.js @@ -414,8 +414,9 @@ export function impliedNumber(value, impliedValue) { if ((value = number(value)) !== impliedValue) return value; } +// https://www.w3.org/TR/CSS21/grammar.html const validClassName = - /^-?([_a-z]|[\240-\377]|\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])([_a-z0-9-]|[\240-\377]|\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])*$/; + /^-?([_a-z]|[\240-\377]|\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])([_a-z0-9-]|[\240-\377]|\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])*$/i; export function maybeClassName(name) { if (name === undefined) return `plot-${Math.random().toString(16).slice(2)}`; diff --git a/test/style-test.js b/test/style-test.js new file mode 100644 index 0000000000..de55220f69 --- /dev/null +++ b/test/style-test.js @@ -0,0 +1,47 @@ +import assert from "assert"; +import {maybeClassName} from "../src/style.js"; + +it("maybeClassName allows typical class names", () => { + assert.strictEqual(maybeClassName("foo"), "foo"); + assert.strictEqual(maybeClassName("foo2"), "foo2"); + assert.strictEqual(maybeClassName("foo-bar"), "foo-bar"); + assert.strictEqual(maybeClassName("-bar"), "-bar"); +}); + +it("maybeClassName allows escaped class names", () => { + assert.strictEqual(maybeClassName("\\1234"), "\\1234"); + assert.strictEqual(maybeClassName("\\64"), "\\64"); + assert.strictEqual(maybeClassName("\\ "), "\\ "); +}); + +it("maybeClassName allows mixed-case class names", () => { + assert.strictEqual(maybeClassName("FOO"), "FOO"); + assert.strictEqual(maybeClassName("fooBar"), "fooBar"); +}); + +it("maybeClassName disallows invalid class names", () => { + assert.throws(() => maybeClassName(" "), /invalid class name/); + assert.throws(() => maybeClassName("42"), /invalid class name/); + assert.throws(() => maybeClassName("foo "), /invalid class name/); + assert.throws(() => maybeClassName(" foo"), /invalid class name/); + assert.throws(() => maybeClassName(".foo"), /invalid class name/); + assert.throws(() => maybeClassName("[foo]"), /invalid class name/); + assert.throws(() => maybeClassName("💩"), /invalid class name/); + assert.throws(() => maybeClassName("--hi"), /invalid class name/); + assert.throws(() => maybeClassName("\\"), /invalid class name/); +}); + +it("maybeClassName coerces to strings", () => { + assert.strictEqual(maybeClassName(["foo"]), "foo"); + assert.strictEqual(maybeClassName({toString: () => "foo-bar"}), "foo-bar"); +}); + +it("maybeClassName generates distinct random class names", () => { + const names = new Set(); + for (let i = 0; i < 100; ++i) { + const name = maybeClassName(); + assert.match(name, /^plot-[0-9a-f]{6,}$/); + assert.strictEqual(names.has(name), false); + names.add(name); + } +});