import { assert } from "chai"; import { getCssSelector, cssSelectorGenerator } from "../src"; describe("CssSelectorGenerator", function () { let root: Element; beforeEach(function () { root = document.body.appendChild(document.createElement("div")); }); afterEach(function () { root.parentNode.removeChild(root); }); describe("basic scenarios", function () { it("should take root element into account", () => { root.innerHTML = `
`; const haystack = root.querySelector("div"); const needle = haystack.querySelector("span"); const result = getCssSelector(needle, { root: haystack }); assert.equal(result, "span"); }); }); describe("special scenarios", function () { it("should not crash on parent-less element", function () { const element = document.createElement("div"); const fn = () => getCssSelector(element); assert.doesNotThrow(fn); }); it("should not throw on descendant selector within root", () => { root.innerHTML = "
"; const selectorTarget = root.querySelector("div span"); const selectorRoot = root.querySelector("div"); const fn = () => getCssSelector(selectorTarget, { root: selectorRoot }); assert.doesNotThrow(fn); }); it("should not timeout on element producing too many combinations", () => { const classNames = Array(100) .fill("") .map((_, index) => `class${String(index)}`) .join(" "); root.innerHTML = `
`; const start = Date.now(); getCssSelector(root.firstElementChild, { maxCombinations: 100 }); const end = Date.now(); assert.isBelow(end - start, 100); }); }); describe("generator", () => { it("should yield multiple selectors", () => { root.innerHTML = "
"; const selectorTarget = root.firstElementChild; const generator = cssSelectorGenerator(selectorTarget, { maxResults: 10, }); const result = [...generator]; assert.deepEqual(result, [ ".aaa", ".bbb", ".ccc", ".aaa.bbb", ".aaa.ccc", ".bbb.ccc", ".aaa.bbb.ccc", "div.aaa", "div.bbb", "div.ccc", ]); }); }); describe("ignoreGeneratedClassNames option", () => { it("should prefer word-like classes over generated ones", () => { root.innerHTML = `
`; const result = getCssSelector(root.firstElementChild, { root: root, selectors: ["class", "id", "tag"], ignoreGeneratedClassNames: true, }); assert.equal(result, ".container"); }); it("should work with mixed generated and regular classes", () => { root.innerHTML = '
'; const result = getCssSelector(root.firstElementChild, { ignoreGeneratedClassNames: true, }); assert.equal(result, ".button-primary"); }); it("should fall back to tag selector when all classes are generated", () => { root.innerHTML = ''; const result = getCssSelector(root.firstElementChild, { ignoreGeneratedClassNames: true, }); assert.equal(result, "span"); }); it("should fall back to nth-child when only generated class present", () => { root.innerHTML = ` `; const result = getCssSelector(root.firstElementChild, { root: root, selectors: ["class", "tag", "nthchild"], includeTag: true, ignoreGeneratedClassNames: true, }); assert.equal(result, "span:nth-child(1)"); }); it("should work with unique generated classes when option is disabled", () => { root.innerHTML = `
`; const result = getCssSelector(root.firstElementChild, { ignoreGeneratedClassNames: false, }); assert.equal(result, ".css-unique123"); }); it("should demonstrate regular classes preferred even when not unique", () => { root.innerHTML = `
`; const result = getCssSelector(root.firstElementChild, { root: root, selectors: ["class", "id", "tag", "nthchild"], ignoreGeneratedClassNames: true, }); // "button" is shared, so should fall back to combining with tag or nth-child // The important thing is that it doesn't use the unique generated class assert.notEqual(result, ".css-unique1"); // Should use button with nth-child or similar, not the generated class assert.match(result, /button|:nth-child/); }); it("should work with BEM notation", () => { root.innerHTML = `
`; const result = getCssSelector(root.firstElementChild, { ignoreGeneratedClassNames: true, }); assert.equal(result, ".block__element--modifier"); }); it("should work with camelCase class names", () => { root.innerHTML = `
`; const result = getCssSelector(root.firstElementChild, { ignoreGeneratedClassNames: true, }); assert.equal(result, ".navButton"); }); it("should work with kebab-case class names", () => { root.innerHTML = `
`; const result = getCssSelector(root.firstElementChild, { ignoreGeneratedClassNames: true, }); assert.equal(result, ".navbar-button"); }); it("should interact correctly with blacklist option", () => { root.innerHTML = `
`; const result = getCssSelector(root.firstElementChild, { ignoreGeneratedClassNames: true, blacklist: [".button"], }); // Should ignore generated classes (css-unique1) which comes first // and blacklisted "button", leaving "primary" as the selector assert.equal(result, ".primary"); }); it("should interact correctly with whitelist option", () => { root.innerHTML = `
`; const result = getCssSelector(root.firstElementChild, { root: root, selectors: ["class"], ignoreGeneratedClassNames: true, whitelist: [".css-unique"], }); // Whitelist should take precedence, allowing the generated class assert.equal(result, ".css-unique"); }); }); });