Skip to content
Merged
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
49 changes: 49 additions & 0 deletions src/node-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ import type {
Identifier,
Operator,
PreludeOperator,
ContainerQuery,
FeatureRange,
String,
} from './node-types'

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -328,6 +331,52 @@ describe('type narrowing — compile-time', () => {
}
})

test('FeatureRange children are typed as Dimension | PreludeOperator, not Operator', () => {
const atrule = parse('@media (width >= 400px) {}').first_child! as Atrule
const range = atrule.prelude!.first_child!.first_child! as FeatureRange
expectTypeOf(range.children).toEqualTypeOf<(Dimension | PreludeOperator)[]>()

const [op, dim] = range.children
expect(op.type_name).toBe('Operator')
expect(dim.type_name).toBe('Dimension')
})

test('ContainerQuery children include PreludeOperator for and/or/not, without casting', () => {
const atrule = parse('@container style(a: 1) and style(b: 2) {}').first_child! as Atrule
const containerQuery = atrule.prelude!.first_child! as ContainerQuery
expectTypeOf(containerQuery.children).toEqualTypeOf<
(Identifier | MediaFeature | Function | PreludeOperator)[]
>()

const [, op] = containerQuery.children
expect(op.type_name).toBe('Operator')
expect((op as PreludeOperator).text).toBe('and')
})

test('AtrulePrelude children include Identifier for @keyframes/@page/@property preludes', () => {
const atrule = parse('@keyframes spin {}').first_child! as Atrule
const prelude = atrule.prelude! as AtrulePrelude
const [ident] = prelude.children

expect(ident.type_name).toBe('Identifier')
if (ident.type_name === 'Identifier') {
expectTypeOf(ident).toExtend<Identifier>()
expect(ident.text).toBe('spin')
}
})

test('AtrulePrelude children include String for @charset', () => {
const root = parse('@charset "UTF-8";')
const atrule = root.first_child! as Atrule
const prelude = atrule.prelude! as AtrulePrelude
const [str] = prelude.children

expect(str.type_name).toBe('String')
if (str.type_name === 'String') {
expectTypeOf(str).toExtend<String>()
}
})

test('AnyCss enables switch narrowing', () => {
// This test verifies the discriminated union works for switch narrowing.
// The function must compile without type errors.
Expand Down
8 changes: 6 additions & 2 deletions src/node-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@ export type AtrulePrelude = WithClone<
| Url
// `@supports style(...)` / `selector(...)` function conditions
| Function
// `@page`, `@keyframes`, `@property`, etc. (parse_identifier())
| Identifier
// `@charset` (parse_charset_prelude())
| String
> & { readonly type: typeof AT_RULE_PRELUDE; readonly type_name: 'AtrulePrelude' }
>

Expand Down Expand Up @@ -489,7 +493,7 @@ export type MediaType = Leaf<

export type ContainerQuery = WithClone<
CSSNode &
WithChildren<Identifier | MediaFeature | Function> & {
WithChildren<Identifier | MediaFeature | Function | PreludeOperator> & {
readonly type: typeof CONTAINER_QUERY
readonly type_name: 'ContainerQuery'
}
Expand Down Expand Up @@ -543,7 +547,7 @@ export type PreludeOperator = Leaf<typeof PRELUDE_OPERATOR, 'Operator'>

export type FeatureRange = WithClone<
CSSNode &
WithChildren<Dimension | Operator> & {
WithChildren<Dimension | PreludeOperator> & {
readonly type: typeof FEATURE_RANGE
readonly type_name: 'MediaFeatureRange'
/** The feature name in a range comparison, e.g. "width" from "(width >= 400px)" */
Expand Down