Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/ui/src/utils/__tests__/detectClerkStylesheetUsage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,42 @@ describe('detectStructuralClerkCss', () => {
});
});

describe('should NOT flag .cl-internal-* classes', () => {
test('.cl-internal- class with :has() selector', () => {
mockStyleSheets([
createMockStyleSheet([
createMockStyleRule(
'.cl-internal-go4bxw:has(button:focus-visible)',
'.cl-internal-go4bxw:has(button:focus-visible) { }',
),
]),
]);

const hits = detectStructuralClerkCss();
expect(hits).toHaveLength(0);
});

test('.cl-internal- class with descendant selector', () => {
mockStyleSheets([
createMockStyleSheet([createMockStyleRule('.cl-internal-o2kwkh ul', '.cl-internal-o2kwkh ul { }')]),
]);

const hits = detectStructuralClerkCss();
expect(hits).toHaveLength(0);
});

test('tag with descendant .cl-internal- class', () => {
mockStyleSheets([
createMockStyleSheet([
createMockStyleRule('button:hover .cl-internal-gxb76v', 'button:hover .cl-internal-gxb76v { }'),
]),
]);

const hits = detectStructuralClerkCss();
expect(hits).toHaveLength(0);
});
});

describe('should handle CORS-blocked stylesheets gracefully', () => {
test('skips stylesheets that throw on cssRules access', () => {
const blockedSheet = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,22 @@ describe('warnAboutCustomizationWithoutPinning', () => {
});

describe('appearance.elements - should warn', () => {
test('for nested selector referencing .cl-internal- class', () => {
warnAboutCustomizationWithoutPinning({
appearance: {
elements: {
card: {
'& .cl-internal-abc123': { padding: '20px' },
},
},
},
});

expect(logger.warnOnce).toHaveBeenCalledTimes(1);
const message = getWarningMessage();
expect(message).toContain('elements.card "& .cl-internal-abc123"');
});

test('for nested selector with .cl- class reference', () => {
warnAboutCustomizationWithoutPinning({
appearance: {
Expand Down
5 changes: 4 additions & 1 deletion packages/ui/src/utils/cssPatterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
* Used by both stylesheet detection and CSS-in-JS analysis.
*/

// Matches .cl- class selectors (Clerk's internal class prefix)
// Matches .cl- class selectors (Clerk's class prefix)
export const CLERK_CLASS_RE = /\.cl-[A-Za-z0-9_-]+/;

// Matches .cl-internal- class selectors (Clerk's generated internal classes, not user-facing)
export const CLERK_INTERNAL_CLASS_RE = /\.cl-internal-[A-Za-z0-9_-]+/g;

// Matches attribute selectors targeting cl- classes (e.g., [class^="cl-"])
export const CLERK_ATTR_RE = /\[\s*class\s*(\^=|\*=|\$=)\s*["']?[^"'\]]*cl-[^"'\]]*["']?\s*\]/i;

Expand Down
13 changes: 11 additions & 2 deletions packages/ui/src/utils/detectClerkStylesheetUsage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CLERK_ATTR_RE, CLERK_CLASS_RE, HAS_RE, POSITIONAL_PSEUDO_RE } from './cssPatterns';
import { CLERK_ATTR_RE, CLERK_CLASS_RE, CLERK_INTERNAL_CLASS_RE, HAS_RE, POSITIONAL_PSEUDO_RE } from './cssPatterns';

type ClerkStructuralHit = {
stylesheetHref: string | null;
Expand All @@ -7,8 +7,17 @@ type ClerkStructuralHit = {
reason: string[];
};

/**
* Strips .cl-internal-* classes from a selector so they don't trigger detection.
* These are Clerk's own generated classes, not user-facing customization points.
*/
function stripInternalClasses(selector: string): string {
return selector.replace(CLERK_INTERNAL_CLASS_RE, '');
}

function isProbablyClerkSelector(selector: string): boolean {
return CLERK_CLASS_RE.test(selector) || CLERK_ATTR_RE.test(selector);
const stripped = stripInternalClasses(selector);
return CLERK_CLASS_RE.test(stripped) || CLERK_ATTR_RE.test(stripped);
}

// Split by commas safely-ish (won't perfectly handle :is(...) with commas, but good enough)
Expand Down
Loading