Skip to content
25 changes: 21 additions & 4 deletions packages/helpers/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,10 @@ export class CSSHelp {
this.doc = doc;
}

private selectorHasUniversal(selector: string): boolean {
return /(^|\s|\+|>|~)\*/.test(selector);
}

private _getStyleRules() {
const styleSheet = this.getStyleSheet();
return this.styleSheetToCssRulesArray(styleSheet).filter(
Expand All @@ -486,10 +490,23 @@ export class CSSHelp {
}

getStyle(selector: string): ExtendedStyleDeclaration | null {
const style = this._getStyleRules().find(
(ele) => ele?.selectorText === selector,
)?.style as ExtendedStyleDeclaration | undefined;
if (!style) return null;
const wantsUniversal = this.selectorHasUniversal(selector);

const rule = this._getStyleRules().find((ele) => {
if (!ele?.selectorText) return false;

const ruleHasUniversal = this.selectorHasUniversal(ele.selectorText);

// Block universal selector leakage
if (ruleHasUniversal && !wantsUniversal) return false;

return ele.selectorText === selector;
});

if (!rule) return null;

const style = rule.style as ExtendedStyleDeclaration;

style.getPropVal = (prop: string, strip = false) =>
strip
? style.getPropertyValue(prop).replace(/\s+/g, "")
Expand Down
14 changes: 14 additions & 0 deletions packages/tests/__fixtures__/curriculum-helper-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,11 +305,25 @@ body {
.card:hover {
background-color: khaki;
}

`;
export const cssWithUniversal = `
span[class~="one"] *:first-of-type {
border-color: #d61;
}
`;

export const cssWithoutUniversal = `
span[class~="one"] > p:first-of-type {
color: red;
}
`;

const testValues = {
cssFullExample,
cssCodeWithCommentsRemoved,
cssWithUniversal,
cssWithoutUniversal,
};

export default testValues;
81 changes: 80 additions & 1 deletion packages/tests/curriculum-helper.test.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the changes to this file are only some empty lines added, please revert

Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ import React from "react";
import ReactDOM from "react-dom/client";

import cssTestValues from "./__fixtures__/curriculum-helper-css";

import htmlTestValues from "./__fixtures__/curriculum-helpers-html";
import jsTestValues from "./__fixtures__/curriculum-helpers-javascript";
import whiteSpaceTestValues from "./__fixtures__/curriculum-helpers-remove-white-space";

import * as helper from "./../helpers/lib/index";

const { stringWithWhiteSpaceChars, stringWithWhiteSpaceCharsRemoved } =
whiteSpaceTestValues;

const { cssFullExample, cssCodeWithCommentsRemoved } = cssTestValues;
const {
cssFullExample,
cssCodeWithCommentsRemoved,
cssWithUniversal,
cssWithoutUniversal,
} = cssTestValues;
Copy link
Contributor

@majestic-owl448 majestic-owl448 Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have these but you are not using these, or you use them or you don't import them


const { htmlFullExample, htmlCodeWithCommentsRemoved } = htmlTestValues;

Expand Down Expand Up @@ -862,3 +869,75 @@ describe("permutateRegex", () => {
expect(regex.test(`'b" === a`)).toBe(false);
});
});

describe("CSSHelp – universal selector handling", () => {
afterEach(() => {
document.head.innerHTML = "";
});

it("want universal selector, universal selector in css", () => {
const styleEl = document.createElement("style");
styleEl.textContent = `
span[class~="one"] *:first-of-type {
border-color: #d61;
}
`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do this when you have create the strings in the fixtures file?

document.head.appendChild(styleEl);

const cssHelp = new helper.CSSHelp(document);

const style = cssHelp.getStyle('span[class~="one"] *:first-of-type');

expect(style).not.toBeNull();
expect(style?.getPropVal("border-color")).toBe("#d61");
});

it("want universal selector, universal selector not in css", () => {
const styleEl = document.createElement("style");
styleEl.textContent = `
span[class~="one"] > p:first-of-type {
color: red;
}
`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, you are importing strings from the fixtures files, so why not use them? instead you are creating this.

I will not add more comments but it's the same below

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out — you’re absolutely right.

I’ve updated the tests to exclusively use the shared fixture strings and removed all inline CSS definitions.

Appreciate your patience and the guidance.

document.head.appendChild(styleEl);

const cssHelp = new helper.CSSHelp(document);

const style = cssHelp.getStyle('span[class~="one"] *:first-of-type');

expect(style).toBeNull();
});

it("don't want universal selector, universal selector in css", () => {
const styleEl = document.createElement("style");
styleEl.textContent = `
span[class~="one"] *:first-of-type {
border-color: #d61;
}
`;
document.head.appendChild(styleEl);

const cssHelp = new helper.CSSHelp(document);

const style = cssHelp.getStyle('span[class~="one"] > p:first-of-type');

expect(style).toBeNull();
});

it("don't want universal selector, universal selector not in css", () => {
const styleEl = document.createElement("style");
styleEl.textContent = `
span[class~="one"] > p:first-of-type {
color: red;
}
`;
document.head.appendChild(styleEl);

const cssHelp = new helper.CSSHelp(document);

const style = cssHelp.getStyle('span[class~="one"] > p:first-of-type');

expect(style).not.toBeNull();
expect(style?.getPropVal("color")).toBe("red");
});
});