From 8d1de41e32707fead5e265fd3505c650bb7e0315 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 16 Mar 2023 11:49:11 -0700 Subject: [PATCH 1/2] allow mixed-case class names --- src/style.js | 2 +- test/style-test.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 test/style-test.js diff --git a/src/style.js b/src/style.js index d7f1ef0975..5938f998f5 100644 --- a/src/style.js +++ b/src/style.js @@ -415,7 +415,7 @@ export function impliedNumber(value, impliedValue) { } 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..ea9b41e034 --- /dev/null +++ b/test/style-test.js @@ -0,0 +1,35 @@ +import assert from "assert"; +import {maybeClassName} from "../src/style.js"; + +it("maybeClassName allows typical class names", () => { + assert.strictEqual(maybeClassName("foo"), "foo"); + assert.strictEqual(maybeClassName("foo-bar"), "foo-bar"); +}); + +it("maybeClassName allows uppercase class names", () => { + assert.strictEqual(maybeClassName("FOO"), "FOO"); + assert.strictEqual(maybeClassName("fooBar"), "fooBar"); +}); + +it("maybeClassName disallows invalid class names", () => { + 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/); +}); + +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); + } +}); From a6b5f8ec8d81c78a22f50704f99cef3a566162b4 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 16 Mar 2023 12:30:41 -0700 Subject: [PATCH 2/2] more tests; cite spec --- src/style.js | 1 + test/style-test.js | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/style.js b/src/style.js index 5938f998f5..e247afece4 100644 --- a/src/style.js +++ b/src/style.js @@ -414,6 +414,7 @@ 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])*$/i; diff --git a/test/style-test.js b/test/style-test.js index ea9b41e034..de55220f69 100644 --- a/test/style-test.js +++ b/test/style-test.js @@ -3,20 +3,32 @@ 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 uppercase class names", () => { +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", () => {