import dedent from 'dedent'
import { assert, expect, test } from 'vitest'
import { toCss, type AstNode } from '../ast'
import * as CSS from '../css-parser'
import { createTranslationMap } from './source-map'
import { visualizeSourceMapRanges, type SourceMapVisualizationRange } from './visualize-source-map'
async function analyze(input: string) {
let ast = CSS.parse(input, { from: 'input.css' })
let css = toCss(ast, true)
let translate = createTranslationMap({
original: input,
generated: css,
})
function format(node: AstNode) {
let ranges: SourceMapVisualizationRange[] = []
for (let [oStart, oEnd, gStart, gEnd] of translate(node)) {
ranges.push({
original: {
source: 'input.css',
start: [oStart.line, oStart.column],
end: [oEnd.line, oEnd.column],
},
generated:
gStart && gEnd
? {
start: [gStart.line, gStart.column],
end: [gEnd.line, gEnd.column],
}
: null,
})
}
return visualizeSourceMapRanges({ 'input.css': input }, css, ranges)
}
return { ast, css, format }
}
test('comment, single line', async () => {
let { ast, css, format } = await analyze(`/*! foo */`)
assert(ast[0].kind === 'comment')
expect(format(ast[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 /*! foo */ | 1 /*! foo */
^^^^^^^^^^ A @ 1:0-10 | ^^^^^^^^^^ A @ 1:0-10
2 |
"
`)
expect(css).toMatchInlineSnapshot(`
"/*! foo */
"
`)
})
test('comment, multi line', async () => {
let { ast, css, format } = await analyze(`/*! foo \n bar */`)
assert(ast[0].kind === 'comment')
expect(format(ast[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 /*! foo | 1 /*! foo
^^^^^^^^ A @ 1:0-2:7 | ^^^^^^^^ A @ 1:0-2:7
2 bar */ | 2 bar */
^^^^^^^ A | ^^^^^^^ A
3 |
"
`)
expect(css).toMatchInlineSnapshot(`
"/*! foo
bar */
"
`)
})
test('declaration, normal property, single line', async () => {
let { ast, css, format } = await analyze(`.foo { color: red; }`)
assert(ast[0].kind === 'rule')
assert(ast[0].nodes[0].kind === 'declaration')
expect(format(ast[0].nodes[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 .foo { |
2 color: red; | 1 .foo { color: red; }
^^^^^^^^^^ A @ 2:2-12 | ^^^^^^^^^^ A @ 1:7-17
3 } |
4 |
"
`)
expect(css).toMatchInlineSnapshot(`
".foo {
color: red;
}
"
`)
})
test('declaration, normal property, multi line', async () => {
let { ast, css, format } = await analyze(dedent`
.foo {
grid-template-areas:
"a b c"
"d e f"
"g h i";
}
`)
assert(ast[0].kind === 'rule')
assert(ast[0].nodes[0].kind === 'declaration')
expect(format(ast[0].nodes[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 .foo { |
2 grid-template-areas: "a b c" "d e f" "g h i"; | 2 grid-template-areas:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A @ 2:2-46 | ^^^^^^^^^^^^^^^^^^^^ A @ 2:2-5:11
| 3 "a b c"
| ^^^^^^^^^^^ A
| 4 "d e f"
| ^^^^^^^^^^^ A
| 5 "g h i";
| ^^^^^^^^^^^ A
| 6 }
3 } |
4 |
"
`)
expect(css).toMatchInlineSnapshot(`
".foo {
grid-template-areas: "a b c" "d e f" "g h i";
}
"
`)
})
test('declaration, custom property, single line', async () => {
let { ast, css, format } = await analyze(`.foo { --foo: bar; }`)
assert(ast[0].kind === 'rule')
assert(ast[0].nodes[0].kind === 'declaration')
expect(format(ast[0].nodes[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 .foo { |
2 --foo: bar; | 1 .foo { --foo: bar; }
^^^^^^^^^^ A @ 2:2-12 | ^^^^^^^^^^ A @ 1:7-17
3 } |
4 |
"
`)
expect(css).toMatchInlineSnapshot(`
".foo {
--foo: bar;
}
"
`)
})
test('declaration, custom property, multi line', async () => {
let { ast, css, format } = await analyze(dedent`
.foo {
--foo: bar\nbaz;
}
`)
assert(ast[0].kind === 'rule')
assert(ast[0].nodes[0].kind === 'declaration')
expect(format(ast[0].nodes[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 .foo { |
2 --foo: bar | 2 --foo: bar
^^^^^^^^^^ A @ 2:2-3:3 | ^^^^^^^^^^ A @ 2:2-3:3
3 baz; | 3 baz;
^^^ A | ^^^ A
| 4 }
4 } |
5 |
"
`)
expect(css).toMatchInlineSnapshot(`
".foo {
--foo: bar
baz;
}
"
`)
})
test('at rules, bodyless, single line', async () => {
let { ast, css, format } = await analyze(`@layer foo, bar;`)
assert(ast[0].kind === 'at-rule')
expect(format(ast[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 @layer foo, bar; | 1 @layer foo, bar;
^^^^^^^^^^^^^^^ A @ 1:0-15 | ^^^^^^^^^^^^^^^^^^^ A @ 1:0-19
2 |
"
`)
expect(css).toMatchInlineSnapshot(`
"@layer foo, bar;
"
`)
})
test('at rules, bodyless, multi line', async () => {
let { ast, css, format } = await analyze(dedent`
@layer
foo,
bar
;
`)
assert(ast[0].kind === 'at-rule')
expect(format(ast[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 @layer foo, bar; | 1 @layer
^^^^^^^^^^^^^^^ A @ 1:0-15 | ^^^^^^ A @ 1:0-4:0
| 2 foo,
| ^^^^^^ A
| 3 bar
| ^^^^^ A
2 |
"
`)
expect(css).toMatchInlineSnapshot(`
"@layer foo, bar;
"
`)
})
test('at rules, body, single line', async () => {
let { ast, css, format } = await analyze(`@layer foo { color: red; }`)
assert(ast[0].kind === 'at-rule')
expect(format(ast[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 @layer foo { | 1 @layer foo { color: red; }
^^^^^^^^^^^ A @ 1:0-11 | ^^^^^^^^^^^ A @ 1:0-11
2 color: red; |
3 } |
4 |
"
`)
expect(css).toMatchInlineSnapshot(`
"@layer foo {
color: red;
}
"
`)
})
test('at rules, body, multi line', async () => {
let { ast, css, format } = await analyze(dedent`
@layer
foo
{
color: baz;
}
`)
assert(ast[0].kind === 'at-rule')
expect(format(ast[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 @layer foo { | 1 @layer
^^^^^^^^^^^ A @ 1:0-11 | ^^^^^^ A @ 1:0-3:0
| 2 foo
| ^^^^^ A
2 color: baz; |
3 } |
4 |
"
`)
expect(css).toMatchInlineSnapshot(`
"@layer foo {
color: baz;
}
"
`)
})
test('style rules, body, single line', async () => {
let { ast, css, format } = await analyze(`.foo:is(.bar) { color: red; }`)
assert(ast[0].kind === 'rule')
expect(format(ast[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 .foo:is(.bar) { | 1 .foo:is(.bar) { color: red; }
^^^^^^^^^^^^^^ A @ 1:0-14 | ^^^^^^^^^^^^^^ A @ 1:0-14
2 color: red; |
3 } |
4 |
"
`)
expect(css).toMatchInlineSnapshot(`
".foo:is(.bar) {
color: red;
}
"
`)
})
test('style rules, body, multi line', async () => {
let { ast, css, format } = await analyze(dedent`
.foo:is(
.bar
) {
color: red;
}
`)
assert(ast[0].kind === 'rule')
expect(format(ast[0])).toMatchInlineSnapshot(`
"
output.css | input.css
|
1 .foo:is( .bar ) { | 1 .foo:is(
^^^^^^^^^^^^^^^^ A @ 1:0-16 | ^^^^^^^^ A @ 1:0-3:2
| 2 .bar
| ^^^^^^ A
| 3 ) {
| ^^ A
2 color: red; |
3 } |
4 |
"
`)
expect(css).toMatchInlineSnapshot(`
".foo:is( .bar ) {
color: red;
}
"
`)
})