Skip to content

Commit b2f3da6

Browse files
dogmarclaude
andcommitted
test: pin literal-type evaluation of marker chains in css-typing.ts
Add type-level assertions verifying that `marker(...).is.<relation>` evaluates to the exact runtime selector string at the type level, modulo the unknown hash slot (replaced with the literal `<HASH>` placeholder by codegen-patch's renderMarkerTypes). Coverage: - call form (`marker("&:has(.flag-error)").is.ancestor`) - underscore form on a registered condition (`cardMarker._hover.is.{ancestor, descendant}`) - all five relations (siblingBefore/siblingAfter/siblingAny) - multi-`&` substitution - parent-nesting condition value (anchor lands in leading position) These are pure type-level checks: each constant assigns the chain result to its expected literal string type. If the recursive `BearbonesSubstituteAmp` type ever loses concreteness (e.g. widens to `${string}` somewhere), one of these assignments fails and `tsc` in the website build flags it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 472591e commit b2f3da6

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

‎apps/website/src/Demo.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export function Demo() {
2626
<h2
2727
className={css("text-lg", "font-bold", {
2828
[cardMarker._hover.is.ancestor]: "text-blue-500",
29+
[cardMarker(":hover").is.siblingAny]: "text-blue-500",
2930
})}
3031
>
3132
Card title

‎apps/website/src/__type-tests__/css-typing.ts‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,60 @@ css({
7575
padding: "4",
7676
});
7777

78+
// --- Literal-string evaluation -----------------------------------------
79+
//
80+
// Every `is.<relation>` chain evaluates at the *type level* to the exact
81+
// raw selector string the runtime emits, modulo the marker hash slot
82+
// (which TypeScript cannot compute — the runtime uses an 8-hex SHA1 of
83+
// `(id, modulePath)`; the type uses the literal placeholder `<HASH>`).
84+
//
85+
// We pin the literal types in both directions to verify the recursive
86+
// `BearbonesSubstituteAmp` substitution stays concrete and doesn't widen
87+
// to `${string}` at any point.
88+
89+
// Call form, single `&`.
90+
const _ancestorCall: ":where(.bearbones-marker-card_<HASH>:has(.flag-error)) &" =
91+
cardMarker("&:has(.flag-error)").is.ancestor;
92+
void _ancestorCall;
93+
94+
// Underscore form picks up the resolved condition value from the conditions
95+
// stash (preset-base default for `_hover` is `&:is(:hover, [data-hover])`).
96+
const _ancestorHover: ":where(.bearbones-marker-card_<HASH>:is(:hover, [data-hover])) &" =
97+
cardMarker._hover.is.ancestor;
98+
void _ancestorHover;
99+
100+
// Descendant relation — self-nesting form, `&` at the front.
101+
const _descendantHover: "&:where(:has(.bearbones-marker-card_<HASH>:is(:hover, [data-hover])))" =
102+
cardMarker._hover.is.descendant;
103+
void _descendantHover;
104+
105+
// Sibling-before, sibling-after, sibling-any — the three new relations.
106+
const _siblingBefore: ":where(.bearbones-marker-card_<HASH>:focus-within) ~ &" =
107+
cardMarker("&:focus-within").is.siblingBefore;
108+
void _siblingBefore;
109+
110+
const _siblingAfter: "&:where(:has(~ .bearbones-marker-card_<HASH>:focus-within))" =
111+
cardMarker("&:focus-within").is.siblingAfter;
112+
void _siblingAfter;
113+
114+
// Comma-joined two-branch shape. The `&`-prefixed branch comes first so the
115+
// literal type satisfies Panda's `AnySelector` (`${string}&` | `&${string}`).
116+
const _siblingAny: "&:where(:has(~ .bearbones-marker-card_<HASH>:focus-within)), :where(.bearbones-marker-card_<HASH>:focus-within) ~ &" =
117+
cardMarker("&:focus-within").is.siblingAny;
118+
void _siblingAny;
119+
120+
// Multi-`&` condition value: every `&` is substituted independently, so the
121+
// observer contains the anchor twice.
122+
const _multiAmp: ":where(.foo:has(.bearbones-marker-card_<HASH>) ~ .bearbones-marker-card_<HASH>) &" =
123+
cardMarker(".foo:has(&) ~ &").is.ancestor;
124+
void _multiAmp;
125+
126+
// Parent-nesting condition value (`& :child` style) — substitution leaves
127+
// the anchor in the leading position rather than the trailing one.
128+
const _parentNesting: "&:where(:has([data-state=open] .bearbones-marker-card_<HASH>))" =
129+
cardMarker("[data-state=open] &").is.descendant;
130+
void _parentNesting;
131+
78132
// --- Rejected forms -----------------------------------------------------
79133

80134
// Typo'd utility name — `13` is not a Panda spacing token (the scale jumps

0 commit comments

Comments
 (0)