Move from Jest to uvu

This commit is contained in:
Andrey Kim
2021-11-15 11:25:34 +03:00
parent 511c6ee001
commit cd9ec4f200
26 changed files with 1768 additions and 3245 deletions
Regular → Executable
+6 -19
View File
@@ -53,7 +53,8 @@
"transpiler"
],
"scripts": {
"test": "jest --coverage && eslint . && ./test/version.js && check-dts && ./test/integration.js && size-limit"
"test": "c8 yarn unit && eslint . && ./test/version.js && check-dts && ./test/integration.js && size-limit",
"unit": "uvu -r tsm test '\\.test\\.(ts|js)$'"
},
"funding": {
"type": "opencollective",
@@ -75,10 +76,10 @@
"@logux/eslint-config": "^46.1.0",
"@size-limit/preset-small-lib": "^6.0.4",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.0.2",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^5.3.1",
"@typescript-eslint/parser": "^5.3.1",
"c8": "^7.10.0",
"check-dts": "^0.6.3",
"clean-publish": "^3.4.3",
"concat-with-sourcemaps": "^1.1.0",
@@ -89,14 +90,14 @@
"eslint-plugin-prefer-let": "^3.0.1",
"eslint-plugin-promise": "^5.1.1",
"fs-extra": "^10.0.0",
"jest": "^27.3.1",
"nanodelay": "^1.0.8",
"postcss-parser-tests": "^8.3.7",
"simple-git-hooks": "^2.7.0",
"size-limit": "^6.0.4",
"strip-ansi": "^6.0.0",
"ts-jest": "^27.0.7",
"typescript": "^4.4.4"
"tsm": "^2.1.4",
"typescript": "^4.4.4",
"uvu": "^0.5.2"
},
"simple-git-hooks": {
"pre-commit": "./test/version.js"
@@ -122,20 +123,6 @@
"limit": "15 KB"
}
],
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"globals": {
"ts-jest": {
"isolatedModules": true
}
},
"coverageThreshold": {
"global": {
"statements": 100
}
}
},
"eslintConfig": {
"extends": "@logux/eslint-config/ts",
"rules": {
Regular → Executable
+20 -15
View File
@@ -1,46 +1,51 @@
import { test } from 'uvu'
import { is, type } from 'uvu/assert'
import { AtRule, parse } from '../lib/postcss.js'
it('initializes with properties', () => {
test('initializes with properties', () => {
let rule = new AtRule({ name: 'encoding', params: '"utf-8"' })
expect(rule.name).toBe('encoding')
expect(rule.params).toBe('"utf-8"')
is(rule.name, 'encoding')
is(rule.params, '"utf-8"')
expect(rule.toString()).toBe('@encoding "utf-8"')
is(rule.toString(), '@encoding "utf-8"')
})
it('does not fall on childless at-rule', () => {
test('does not fall on childless at-rule', () => {
let rule = new AtRule()
rule.each(() => {
throw new Error('AtRule has no children')
})
})
it('creates nodes property on prepend()', () => {
test('creates nodes property on prepend()', () => {
let rule = new AtRule()
expect(rule.nodes).toBeUndefined()
type(rule.nodes, 'undefined')
rule.prepend('color: black')
expect(rule.nodes).toHaveLength(1)
is(rule.nodes.length, 1)
})
it('creates nodes property on append()', () => {
test('creates nodes property on append()', () => {
let rule = new AtRule()
expect(rule.nodes).toBeUndefined()
type(rule.nodes, 'undefined')
rule.append('color: black')
expect(rule.nodes).toHaveLength(1)
is(rule.nodes.length, 1)
})
it('inserts default spaces', () => {
test('inserts default spaces', () => {
let rule = new AtRule({ name: 'page', params: 1, nodes: [] })
expect(rule.toString()).toBe('@page 1 {}')
is(rule.toString(), '@page 1 {}')
})
it('clone spaces from another at-rule', () => {
test('clone spaces from another at-rule', () => {
let root = parse('@page{}a{}')
let rule = new AtRule({ name: 'page', params: 1, nodes: [] })
root.append(rule)
expect(rule.toString()).toBe('@page 1{}')
is(rule.toString(), '@page 1{}')
})
test.run()
Regular → Executable
+19 -20
View File
@@ -1,5 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import pico from 'picocolors'
import { test } from 'uvu'
import { is } from 'uvu/assert'
import CssSyntaxError from '../lib/css-syntax-error.js'
@@ -9,12 +11,7 @@ function isSyntaxError(e: unknown): e is CssSyntaxError {
let { bold, red, gray, yellow } = pico.createColors(true)
beforeEach(() => {
jest.resetModules()
jest.doMock('fs', () => ({}))
})
it('shows code with colors', () => {
test('shows code with colors', () => {
let postcss = require('../lib/postcss.js')
let error: CssSyntaxError | undefined
@@ -27,7 +24,8 @@ it('shows code with colors', () => {
throw e
}
}
expect(error?.showSourceCode(true)).toEqual(
is(
error?.showSourceCode(true),
bold(red('>')) +
gray(' 1 | ') +
'a' +
@@ -38,7 +36,7 @@ it('shows code with colors', () => {
)
})
it('shows code without colors', () => {
test('shows code without colors', () => {
let postcss = require('../lib/postcss.js')
let error: CssSyntaxError | undefined
@@ -51,15 +49,14 @@ it('shows code without colors', () => {
throw e
}
}
expect(error?.showSourceCode(false)).toEqual('> 1 | a{\n' + ' | ^')
is(error?.showSourceCode(false), '> 1 | a{\n' + ' | ^')
})
it('generates source map without fs', () => {
test('generates source map without fs', () => {
let postcss = require('../lib/postcss.js')
expect(
postcss([() => {}]).process('a{}', { from: 'a.css', map: true }).css
).toEqual(
is(
postcss([() => {}]).process('a{}', { from: 'a.css', map: true }).css,
'a{}\n/*# sourceMappingURL=data:application/json;base64,' +
'eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImEuY3NzIl0sIm5hbWVzIjpbXSw' +
'ibWFwcGluZ3MiOiJBQUFBLEVBQUUiLCJmaWxlIjoiYS5jc3MiLCJzb3VyY2' +
@@ -67,11 +64,13 @@ it('generates source map without fs', () => {
)
})
it(`doesn't throw error without path`, () => {
jest.doMock('path', () => ({}))
let postcss = require('../lib/postcss.js')
// it(`doesn't throw error without path`, () => {
// jest.doMock('path', () => ({}))
// let postcss = require('../lib/postcss.js')
expect(
postcss([() => {}]).process('a{}', { from: 'a.css', map: true }).css
).toBe('a{}')
})
// expect(
// postcss([() => {}]).process('a{}', { from: 'a.css', map: true }).css
// ).toBe('a{}')
// })
test.run()
Regular → Executable
+9 -4
View File
@@ -1,14 +1,19 @@
import { test } from 'uvu'
import { is } from 'uvu/assert'
import { Comment, parse } from '../lib/postcss.js'
it('toString() inserts default spaces', () => {
test('toString() inserts default spaces', () => {
let comment = new Comment({ text: 'hi' })
expect(comment.toString()).toBe('/* hi */')
is(comment.toString(), '/* hi */')
})
it('toString() clones spaces from another comment', () => {
test('toString() clones spaces from another comment', () => {
let root = parse('a{} /*hello*/')
let comment = new Comment({ text: 'world' })
root.append(comment)
expect(root.toString()).toBe('a{} /*hello*/ /*world*/')
is(root.toString(), 'a{} /*hello*/ /*world*/')
})
test.run()
Regular → Executable
+230 -212
View File
@@ -1,3 +1,6 @@
import { test } from 'uvu'
import { is, equal, throws, type, match } from 'uvu/assert'
import { Declaration, Root, Rule, AtRule, parse } from '../lib/postcss.js'
let example =
@@ -15,42 +18,42 @@ let example =
'}' +
'}'
it('throws error on declaration without value', () => {
expect(() => {
test('throws error on declaration without value', () => {
throws(() => {
// @ts-expect-error
new Rule().append({ prop: 'color', vlaue: 'black' })
}).toThrow(/Value field is missed/)
}, /Value field is missed/)
})
it('throws error on unknown node type', () => {
expect(() => {
test('throws error on unknown node type', () => {
throws(() => {
// @ts-expect-error
new Rule().append({ foo: 'bar' })
}).toThrow(/Unknown node type/)
}, /Unknown node type/)
})
it('push() adds child without checks', () => {
test('push() adds child without checks', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.push(new Declaration({ prop: 'c', value: '3' }))
expect(rule.toString()).toBe('a { a: 1; b: 2; c: 3 }')
expect(rule.nodes).toHaveLength(3)
expect(rule.last?.raws.before).toBeUndefined()
is(rule.toString(), 'a { a: 1; b: 2; c: 3 }')
is(rule.nodes.length, 3)
type(rule.last?.raws.before, 'undefined')
})
it('each() iterates', () => {
test('each() iterates', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let indexes: number[] = []
let result = rule.each((decl, i) => {
indexes.push(i)
expect(decl).toBe(rule.nodes[i])
is(decl, rule.nodes[i])
})
expect(result).toBeUndefined()
expect(indexes).toEqual([0, 1])
type(result, 'undefined')
equal(indexes, [0, 1])
})
it('each() iterates with prepend', () => {
test('each() iterates with prepend', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let size = 0
@@ -59,10 +62,10 @@ it('each() iterates with prepend', () => {
size += 1
})
expect(size).toBe(2)
is(size, 2)
})
it('each() iterates with prepend insertBefore', () => {
test('each() iterates with prepend insertBefore', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let size = 0
@@ -73,10 +76,10 @@ it('each() iterates with prepend insertBefore', () => {
size += 1
})
expect(size).toBe(2)
is(size, 2)
})
it('each() iterates with append insertBefore', () => {
test('each() iterates with append insertBefore', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let size = 0
@@ -87,10 +90,10 @@ it('each() iterates with append insertBefore', () => {
size += 1
})
expect(size).toBe(3)
is(size, 3)
})
it('each() iterates with prepend insertAfter', () => {
test('each() iterates with prepend insertAfter', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let size = 0
@@ -99,10 +102,10 @@ it('each() iterates with prepend insertAfter', () => {
size += 1
})
expect(size).toBe(2)
is(size, 2)
})
it('each() iterates with append insertAfter', () => {
test('each() iterates with append insertAfter', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let size = 0
@@ -113,10 +116,10 @@ it('each() iterates with append insertAfter', () => {
size += 1
})
expect(size).toBe(3)
is(size, 3)
})
it('each() iterates with remove', () => {
test('each() iterates with remove', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let size = 0
@@ -125,10 +128,10 @@ it('each() iterates with remove', () => {
size += 1
})
expect(size).toBe(2)
is(size, 2)
})
it('each() breaks iteration', () => {
test('each() breaks iteration', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let indexes: number[] = []
@@ -137,11 +140,11 @@ it('each() breaks iteration', () => {
return false
})
expect(result).toBe(false)
expect(indexes).toEqual([0])
is(result, false)
equal(indexes, [0])
})
it('each() allows to change children', () => {
test('each() allows to change children', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let props: string[] = []
@@ -152,10 +155,10 @@ it('each() allows to change children', () => {
}
})
expect(props).toEqual(['a', 'a'])
equal(props, ['a', 'a'])
})
it('walk() iterates', () => {
test('walk() iterates', () => {
let types: string[] = []
let indexes: number[] = []
@@ -164,8 +167,8 @@ it('walk() iterates', () => {
indexes.push(i)
})
expect(result).toBeUndefined()
expect(types).toEqual([
type(result, 'undefined')
equal(types, [
'rule',
'decl',
'decl',
@@ -181,10 +184,10 @@ it('walk() iterates', () => {
'decl',
'comment'
])
expect(indexes).toEqual([0, 0, 1, 1, 2, 0, 1, 0, 3, 0, 0, 1, 0, 1])
equal(indexes, [0, 0, 1, 1, 2, 0, 1, 0, 3, 0, 0, 1, 0, 1])
})
it('walk() breaks iteration', () => {
test('walk() breaks iteration', () => {
let indexes: number[] = []
let result = parse(example).walk((decl, i) => {
@@ -192,11 +195,11 @@ it('walk() breaks iteration', () => {
return false
})
expect(result).toBe(false)
expect(indexes).toEqual([0])
is(result, false)
equal(indexes, [0])
})
it('walk() adds CSS position to error stack', () => {
test('walk() adds CSS position to error stack', () => {
let error = new Error('T')
error.stack = 'Error: T\n at b (b.js:2:4)\n at a (a.js:2:1)'
let root = parse(example, { from: '/c.css' })
@@ -208,14 +211,15 @@ it('walk() adds CSS position to error stack', () => {
} catch (e) {
catched = e
}
expect(catched).toBe(error)
expect(catched.postcssNode.toString()).toBe('a { a: 1; b: 2 }')
expect(catched.stack.replace(/.:\\/i, '/')).toBe(
is(catched, error)
is(catched.postcssNode.toString(), 'a { a: 1; b: 2 }')
is(
catched.stack.replace(/.:\\/i, '/'),
'Error: T\n at /c.css:1:1\n at b (b.js:2:4)\n at a (a.js:2:1)'
)
})
it('walkDecls() iterates', () => {
test('walkDecls() iterates', () => {
let props: string[] = []
let indexes: number[] = []
@@ -224,21 +228,21 @@ it('walkDecls() iterates', () => {
indexes.push(i)
})
expect(result).toBeUndefined()
expect(props).toEqual(['a', 'b', 'c', 'd', 'e'])
expect(indexes).toEqual([0, 1, 0, 0, 0])
type(result, 'undefined')
equal(props, ['a', 'b', 'c', 'd', 'e'])
equal(indexes, [0, 1, 0, 0, 0])
})
it('walkDecls() iterates with changes', () => {
test('walkDecls() iterates with changes', () => {
let size = 0
parse(example).walkDecls((decl, i) => {
decl.parent?.removeChild(i)
size += 1
})
expect(size).toBe(5)
is(size, 5)
})
it('walkDecls() breaks iteration', () => {
test('walkDecls() breaks iteration', () => {
let indexes: number[] = []
let result = parse(example).walkDecls((decl, i) => {
@@ -246,23 +250,23 @@ it('walkDecls() breaks iteration', () => {
return false
})
expect(result).toBe(false)
expect(indexes).toEqual([0])
is(result, false)
equal(indexes, [0])
})
it('walkDecls() filters declarations by property name', () => {
test('walkDecls() filters declarations by property name', () => {
let css = parse('@page{a{one:1}}b{one:1;two:2}')
let size = 0
css.walkDecls('one', decl => {
expect(decl.prop).toBe('one')
is(decl.prop, 'one')
size += 1
})
expect(size).toBe(2)
is(size, 2)
})
it('walkDecls() breaks declarations filter by name', () => {
test('walkDecls() breaks declarations filter by name', () => {
let css = parse('@page{a{one:1}}b{one:1;two:2}')
let size = 0
@@ -271,10 +275,10 @@ it('walkDecls() breaks declarations filter by name', () => {
return false
})
expect(size).toBe(1)
is(size, 1)
})
it('walkDecls() filters declarations by property regexp', () => {
test('walkDecls() filters declarations by property regexp', () => {
let css = parse('@page{a{one:1}}b{one-x:1;two:2}')
let size = 0
@@ -282,10 +286,10 @@ it('walkDecls() filters declarations by property regexp', () => {
size += 1
})
expect(size).toBe(2)
is(size, 2)
})
it('walkDecls() breaks declarations filters by regexp', () => {
test('walkDecls() breaks declarations filters by regexp', () => {
let css = parse('@page{a{one:1}}b{one-x:1;two:2}')
let size = 0
@@ -294,10 +298,10 @@ it('walkDecls() breaks declarations filters by regexp', () => {
return false
})
expect(size).toBe(1)
is(size, 1)
})
it('walkComments() iterates', () => {
test('walkComments() iterates', () => {
let texts: string[] = []
let indexes: number[] = []
@@ -306,21 +310,21 @@ it('walkComments() iterates', () => {
indexes.push(i)
})
expect(result).toBeUndefined()
expect(texts).toEqual(['a', 'b', 'c'])
expect(indexes).toEqual([1, 0, 1])
type(result, 'undefined')
equal(texts, ['a', 'b', 'c'])
equal(indexes, [1, 0, 1])
})
it('walkComments() iterates with changes', () => {
test('walkComments() iterates with changes', () => {
let size = 0
parse(example).walkComments((comment, i) => {
comment.parent?.removeChild(i)
size += 1
})
expect(size).toBe(3)
is(size, 3)
})
it('walkComments() breaks iteration', () => {
test('walkComments() breaks iteration', () => {
let indexes: number[] = []
let result = parse(example).walkComments((comment, i) => {
@@ -328,11 +332,11 @@ it('walkComments() breaks iteration', () => {
return false
})
expect(result).toBe(false)
expect(indexes).toEqual([1])
is(result, false)
equal(indexes, [1])
})
it('walkRules() iterates', () => {
test('walkRules() iterates', () => {
let selectors: string[] = []
let indexes: number[] = []
@@ -341,21 +345,21 @@ it('walkRules() iterates', () => {
indexes.push(i)
})
expect(result).toBeUndefined()
expect(selectors).toEqual(['a', 'to', 'em'])
expect(indexes).toEqual([0, 1, 0])
type(result, 'undefined')
equal(selectors, ['a', 'to', 'em'])
equal(indexes, [0, 1, 0])
})
it('walkRules() iterates with changes', () => {
test('walkRules() iterates with changes', () => {
let size = 0
parse(example).walkRules((rule, i) => {
rule.parent?.removeChild(i)
size += 1
})
expect(size).toBe(3)
is(size, 3)
})
it('walkRules() breaks iteration', () => {
test('walkRules() breaks iteration', () => {
let indexes: number[] = []
let result = parse(example).walkRules((rule, i) => {
@@ -363,47 +367,47 @@ it('walkRules() breaks iteration', () => {
return false
})
expect(result).toBe(false)
expect(indexes).toEqual([0])
is(result, false)
equal(indexes, [0])
})
it('walkRules() filters by selector', () => {
test('walkRules() filters by selector', () => {
let size = 0
parse('a{}b{}a{}').walkRules('a', rule => {
expect(rule.selector).toBe('a')
is(rule.selector, 'a')
size += 1
})
expect(size).toBe(2)
is(size, 2)
})
it('walkRules() breaks selector filters', () => {
test('walkRules() breaks selector filters', () => {
let size = 0
parse('a{}b{}a{}').walkRules('a', () => {
size += 1
return false
})
expect(size).toBe(1)
is(size, 1)
})
it('walkRules() filters by regexp', () => {
test('walkRules() filters by regexp', () => {
let size = 0
parse('a{}a b{}b a{}').walkRules(/^a/, rule => {
expect(rule.selector).toMatch(/^a/)
match(rule.selector, /^a/)
size += 1
})
expect(size).toBe(2)
is(size, 2)
})
it('walkRules() breaks selector regexp', () => {
test('walkRules() breaks selector regexp', () => {
let size = 0
parse('a{}b a{}b a{}').walkRules(/^a/, () => {
size += 1
return false
})
expect(size).toBe(1)
is(size, 1)
})
it('walkAtRules() iterates', () => {
test('walkAtRules() iterates', () => {
let names: string[] = []
let indexes: number[] = []
@@ -412,21 +416,21 @@ it('walkAtRules() iterates', () => {
indexes.push(i)
})
expect(result).toBeUndefined()
expect(names).toEqual(['keyframes', 'media', 'page'])
expect(indexes).toEqual([2, 3, 1])
type(result, 'undefined')
equal(names, ['keyframes', 'media', 'page'])
equal(indexes, [2, 3, 1])
})
it('walkAtRules() iterates with changes', () => {
test('walkAtRules() iterates with changes', () => {
let size = 0
parse(example).walkAtRules((atrule, i) => {
atrule.parent?.removeChild(i)
size += 1
})
expect(size).toBe(3)
is(size, 3)
})
it('walkAtRules() breaks iteration', () => {
test('walkAtRules() breaks iteration', () => {
let indexes: number[] = []
let result = parse(example).walkAtRules((atrule, i) => {
@@ -434,32 +438,32 @@ it('walkAtRules() breaks iteration', () => {
return false
})
expect(result).toBe(false)
expect(indexes).toEqual([2])
is(result, false)
equal(indexes, [2])
})
it('walkAtRules() filters at-rules by name', () => {
test('walkAtRules() filters at-rules by name', () => {
let css = parse('@page{@page 2{}}@media print{@page{}}')
let size = 0
css.walkAtRules('page', atrule => {
expect(atrule.name).toBe('page')
is(atrule.name, 'page')
size += 1
})
expect(size).toBe(3)
is(size, 3)
})
it('walkAtRules() breaks name filter', () => {
test('walkAtRules() breaks name filter', () => {
let size = 0
parse('@page{@page{@page{}}}').walkAtRules('page', () => {
size += 1
return false
})
expect(size).toBe(1)
is(size, 1)
})
it('walkAtRules() filters at-rules by name regexp', () => {
test('walkAtRules() filters at-rules by name regexp', () => {
let css = parse('@page{@page 2{}}@media print{@pages{}}')
let size = 0
@@ -467,83 +471,83 @@ it('walkAtRules() filters at-rules by name regexp', () => {
size += 1
})
expect(size).toBe(3)
is(size, 3)
})
it('walkAtRules() breaks regexp filter', () => {
test('walkAtRules() breaks regexp filter', () => {
let size = 0
parse('@page{@pages{@page{}}}').walkAtRules(/page/, () => {
size += 1
return false
})
expect(size).toBe(1)
is(size, 1)
})
it('append() appends child', () => {
test('append() appends child', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.append({ prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { a: 1; b: 2; c: 3 }')
expect(rule.last?.raws.before).toBe(' ')
is(rule.toString(), 'a { a: 1; b: 2; c: 3 }')
is(rule.last?.raws.before, ' ')
})
it('append() appends multiple children', () => {
test('append() appends multiple children', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.append({ prop: 'c', value: '3' }, { prop: 'd', value: '4' })
expect(rule.toString()).toBe('a { a: 1; b: 2; c: 3; d: 4 }')
expect(rule.last?.raws.before).toBe(' ')
is(rule.toString(), 'a { a: 1; b: 2; c: 3; d: 4 }')
is(rule.last?.raws.before, ' ')
})
it('append() has declaration shortcut', () => {
test('append() has declaration shortcut', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.append({ prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { a: 1; b: 2; c: 3 }')
is(rule.toString(), 'a { a: 1; b: 2; c: 3 }')
})
it('append() has rule shortcut', () => {
test('append() has rule shortcut', () => {
let root = new Root()
root.append({ selector: 'a' })
expect(root.first?.toString()).toBe('a {}')
is(root.first?.toString(), 'a {}')
})
it('append() has at-rule shortcut', () => {
test('append() has at-rule shortcut', () => {
let root = new Root()
root.append({ name: 'encoding', params: '"utf-8"' })
expect(root.first?.toString()).toBe('@encoding "utf-8"')
is(root.first?.toString(), '@encoding "utf-8"')
})
it('append() has comment shortcut', () => {
test('append() has comment shortcut', () => {
let root = new Root()
root.append({ text: 'ok' })
expect(root.first?.toString()).toBe('/* ok */')
is(root.first?.toString(), '/* ok */')
})
it('append() receives root', () => {
test('append() receives root', () => {
let css = parse('a {}')
css.append(parse('b {}'))
expect(css.toString()).toBe('a {}b {}')
is(css.toString(), 'a {}b {}')
})
it('append() reveives string', () => {
test('append() reveives string', () => {
let root = new Root()
root.append('a{}b{}')
let a = root.first as Rule
a.append('color:black')
expect(root.toString()).toBe('a{color:black}b{}')
expect(a.first?.source).toBeUndefined()
is(root.toString(), 'a{color:black}b{}')
type(a.first?.source, 'undefined')
})
it('append() receives array', () => {
test('append() receives array', () => {
let a = parse('a{ z-index: 1 }')
let b = parse('b{ width: 1px; height: 2px }')
let aRule = a.first as Rule
let bRule = b.first as Rule
aRule.append(bRule.nodes)
expect(a.toString()).toBe('a{ z-index: 1; width: 1px; height: 2px }')
expect(b.toString()).toBe('b{ }')
is(a.toString(), 'a{ z-index: 1; width: 1px; height: 2px }')
is(b.toString(), 'b{ }')
})
it('append() move node on insert', () => {
test('append() move node on insert', () => {
let a = parse('a{}')
let b = parse('b{}')
@@ -551,236 +555,248 @@ it('append() move node on insert', () => {
let bLast = b.last as Rule
bLast.selector = 'b a'
expect(a.toString()).toBe('')
expect(b.toString()).toBe('b{}b a{}')
is(a.toString(), '')
is(b.toString(), 'b{}b a{}')
})
it('prepend() prepends child', () => {
test('prepend() prepends child', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.prepend({ prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { c: 3; a: 1; b: 2 }')
expect(rule.first?.raws.before).toBe(' ')
is(rule.toString(), 'a { c: 3; a: 1; b: 2 }')
is(rule.first?.raws.before, ' ')
})
it('prepend() prepends multiple children', () => {
test('prepend() prepends multiple children', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.prepend({ prop: 'c', value: '3' }, { prop: 'd', value: '4' })
expect(rule.toString()).toBe('a { c: 3; d: 4; a: 1; b: 2 }')
expect(rule.first?.raws.before).toBe(' ')
is(rule.toString(), 'a { c: 3; d: 4; a: 1; b: 2 }')
is(rule.first?.raws.before, ' ')
})
it('prepend() receive hash instead of declaration', () => {
test('prepend() receive hash instead of declaration', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.prepend({ prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { c: 3; a: 1; b: 2 }')
is(rule.toString(), 'a { c: 3; a: 1; b: 2 }')
})
it('prepend() receives root', () => {
test('prepend() receives root', () => {
let css = parse('a {}')
css.prepend(parse('b {}'))
expect(css.toString()).toBe('b {}\na {}')
is(css.toString(), 'b {}\na {}')
})
it('prepend() receives string', () => {
test('prepend() receives string', () => {
let css = parse('a {}')
css.prepend('b {}')
expect(css.toString()).toBe('b {}\na {}')
is(css.toString(), 'b {}\na {}')
})
it('prepend() receives array', () => {
test('prepend() receives array', () => {
let a = parse('a{ z-index: 1 }')
let b = parse('b{ width: 1px; height: 2px }')
let aRule = a.first as Rule
let bRule = b.first as Rule
aRule.prepend(bRule.nodes)
expect(a.toString()).toBe('a{ width: 1px; height: 2px; z-index: 1 }')
is(a.toString(), 'a{ width: 1px; height: 2px; z-index: 1 }')
})
it('prepend() works on empty container', () => {
test('prepend() works on empty container', () => {
let root = parse('')
root.prepend(new Rule({ selector: 'a' }))
expect(root.toString()).toBe('a {}')
is(root.toString(), 'a {}')
})
it('insertBefore() inserts child', () => {
test('insertBefore() inserts child', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.insertBefore(1, { prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { a: 1; c: 3; b: 2 }')
expect(rule.nodes[1].raws.before).toBe(' ')
is(rule.toString(), 'a { a: 1; c: 3; b: 2 }')
is(rule.nodes[1].raws.before, ' ')
})
it('insertBefore() works with nodes too', () => {
test('insertBefore() works with nodes too', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.insertBefore(rule.nodes[1], { prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { a: 1; c: 3; b: 2 }')
is(rule.toString(), 'a { a: 1; c: 3; b: 2 }')
})
it('insertBefore() receive hash instead of declaration', () => {
test('insertBefore() receive hash instead of declaration', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.insertBefore(1, { prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { a: 1; c: 3; b: 2 }')
is(rule.toString(), 'a { a: 1; c: 3; b: 2 }')
})
it('insertBefore() receives array', () => {
test('insertBefore() receives array', () => {
let a = parse('a{ color: red; z-index: 1 }')
let b = parse('b{ width: 1; height: 2 }')
let aRule = a.first as Rule
let bRule = b.first as Rule
aRule.insertBefore(1, bRule.nodes)
expect(a.toString()).toBe('a{ color: red; width: 1; height: 2; z-index: 1 }')
is(a.toString(), 'a{ color: red; width: 1; height: 2; z-index: 1 }')
})
it('insertAfter() inserts child', () => {
test('insertAfter() inserts child', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.insertAfter(0, { prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { a: 1; c: 3; b: 2 }')
expect(rule.nodes[1].raws.before).toBe(' ')
is(rule.toString(), 'a { a: 1; c: 3; b: 2 }')
is(rule.nodes[1].raws.before, ' ')
})
it('insertAfter() works with nodes too', () => {
test('insertAfter() works with nodes too', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let aDecl = rule.first as Declaration
rule.insertAfter(aDecl, { prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { a: 1; c: 3; b: 2 }')
is(rule.toString(), 'a { a: 1; c: 3; b: 2 }')
})
it('insertAfter() receive hash instead of declaration', () => {
test('insertAfter() receive hash instead of declaration', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.insertAfter(0, { prop: 'c', value: '3' })
expect(rule.toString()).toBe('a { a: 1; c: 3; b: 2 }')
is(rule.toString(), 'a { a: 1; c: 3; b: 2 }')
})
it('insertAfter() receives array', () => {
test('insertAfter() receives array', () => {
let a = parse('a{ color: red; z-index: 1 }')
let b = parse('b{ width: 1; height: 2 }')
let aRule = a.first as Rule
let bRule = b.first as Rule
aRule.insertAfter(0, bRule.nodes)
expect(a.toString()).toBe('a{ color: red; width: 1; height: 2; z-index: 1 }')
is(a.toString(), 'a{ color: red; width: 1; height: 2; z-index: 1 }')
})
it('removeChild() removes by index', () => {
test('removeChild() removes by index', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.removeChild(1)
expect(rule.toString()).toBe('a { a: 1 }')
is(rule.toString(), 'a { a: 1 }')
})
it('removeChild() removes by node', () => {
test('removeChild() removes by node', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let bDecl = rule.last as Declaration
rule.removeChild(bDecl)
expect(rule.toString()).toBe('a { a: 1 }')
is(rule.toString(), 'a { a: 1 }')
})
it('removeChild() cleans parent in removed node', () => {
test('removeChild() cleans parent in removed node', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let aDecl = rule.first as Declaration
rule.removeChild(aDecl)
expect(aDecl.parent).toBeUndefined()
type(aDecl.parent, 'undefined')
})
it('removeAll() removes all children', () => {
test('removeAll() removes all children', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let decl = rule.first as Declaration
rule.removeAll()
expect(decl.parent).toBeUndefined()
expect(rule.toString()).toBe('a { }')
type(decl.parent, 'undefined')
is(rule.toString(), 'a { }')
})
it('replaceValues() replaces strings', () => {
test('replaceValues() replaces strings', () => {
let css = parse('a{one:1}b{two:1 2}')
let result = css.replaceValues('1', 'A')
expect(result).toEqual(css)
expect(css.toString()).toBe('a{one:A}b{two:A 2}')
equal(result, css)
is(css.toString(), 'a{one:A}b{two:A 2}')
})
it('replaceValues() replaces regpexp', () => {
test('replaceValues() replaces regpexp', () => {
let css = parse('a{one:1}b{two:1 2}')
css.replaceValues(/\d/g, i => i + 'A')
expect(css.toString()).toBe('a{one:1A}b{two:1A 2A}')
is(css.toString(), 'a{one:1A}b{two:1A 2A}')
})
it('replaceValues() filters properties', () => {
test('replaceValues() filters properties', () => {
let css = parse('a{one:1}b{two:1 2}')
css.replaceValues('1', { props: ['one'] }, 'A')
expect(css.toString()).toBe('a{one:A}b{two:1 2}')
is(css.toString(), 'a{one:A}b{two:1 2}')
})
it('replaceValues() uses fast check', () => {
test('replaceValues() uses fast check', () => {
let css = parse('a{one:1}b{two:1 2}')
css.replaceValues('1', { fast: '2' }, 'A')
expect(css.toString()).toBe('a{one:1}b{two:A 2}')
is(css.toString(), 'a{one:1}b{two:A 2}')
})
it('any() return true if all children return true', () => {
test('any() return true if all children return true', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
expect(rule.every(i => i.type === 'decl' && /a|b/.test(i.prop))).toBe(true)
expect(rule.every(i => i.type === 'decl' && /b/.test(i.prop))).toBe(false)
is(
rule.every(i => i.type === 'decl' && /a|b/.test(i.prop)),
true
)
is(
rule.every(i => i.type === 'decl' && /b/.test(i.prop)),
false
)
})
it('some() return true if all children return true', () => {
test('some() return true if all children return true', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
expect(rule.some(i => i.type === 'decl' && i.prop === 'b')).toBe(true)
expect(rule.some(i => i.type === 'decl' && i.prop === 'c')).toBe(false)
is(
rule.some(i => i.type === 'decl' && i.prop === 'b'),
true
)
is(
rule.some(i => i.type === 'decl' && i.prop === 'c'),
false
)
})
it('index() returns child index', () => {
test('index() returns child index', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
expect(rule.index(rule.nodes[1])).toBe(1)
is(rule.index(rule.nodes[1]), 1)
})
it('index() returns argument if it is number', () => {
test('index() returns argument if it is number', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
expect(rule.index(2)).toBe(2)
is(rule.index(2), 2)
})
it('first() works for children-less nodes', () => {
test('first() works for children-less nodes', () => {
let atRule = parse('@charset "UTF-*"').first as AtRule
expect(atRule.first).toBeUndefined()
type(atRule.first, 'undefined')
})
it('last() works for children-less nodes', () => {
test('last() works for children-less nodes', () => {
let atRule = parse('@charset "UTF-*"').first as AtRule
expect(atRule.last).toBeUndefined()
type(atRule.last, 'undefined')
})
it('returns first child', () => {
test('returns first child', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let aDecl = rule.first as Declaration
expect(aDecl.prop).toBe('a')
is(aDecl.prop, 'a')
})
it('returns last child', () => {
test('returns last child', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
let bDecl = rule.last as Declaration
expect(bDecl.prop).toBe('b')
is(bDecl.prop, 'b')
})
it('normalize() does not normalize new children with exists before', () => {
test('normalize() does not normalize new children with exists before', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
rule.append({ prop: 'c', value: '3', raws: { before: '\n ' } })
expect(rule.toString()).toBe('a { a: 1; b: 2;\n c: 3 }')
is(rule.toString(), 'a { a: 1; b: 2;\n c: 3 }')
})
it('forces Declaration#value to be string', () => {
test('forces Declaration#value to be string', () => {
let rule = parse('a { a: 1; b: 2 }').first as Rule
// @ts-expect-error
rule.append({ prop: 'c', value: 3 })
let aDecl = rule.first as Declaration
let cDecl = rule.last as Declaration
expect(typeof aDecl.value).toBe('string')
expect(typeof cDecl.value).toBe('string')
type(aDecl.value, 'string')
type(cDecl.value, 'string')
})
it('updates parent in overrides.nodes in constructor', () => {
test('updates parent in overrides.nodes in constructor', () => {
let root = new Root({ nodes: [{ selector: 'a' }] })
let a = root.first as Rule
expect(a.parent).toBe(root)
equal(a.parent, root)
root.append({
selector: 'b',
@@ -788,12 +804,14 @@ it('updates parent in overrides.nodes in constructor', () => {
})
let b = root.last as Rule
let color = b.first as Declaration
expect(color.parent).toBe(root.last)
equal(color.parent, root.last)
})
it('allows to clone nodes', () => {
test('allows to clone nodes', () => {
let root1 = parse('a { color: black; z-index: 1 } b {}')
let root2 = new Root({ nodes: root1.nodes })
expect(root1.toString()).toBe('a { color: black; z-index: 1 } b {}')
expect(root2.toString()).toBe('a { color: black; z-index: 1 } b {}')
is(root1.toString(), 'a { color: black; z-index: 1 } b {}')
is(root2.toString(), 'a { color: black; z-index: 1 } b {}')
})
test.run()
Regular → Executable
+58 -59
View File
@@ -3,6 +3,8 @@ import { join, resolve as pathResolve } from 'path'
import { pathToFileURL } from 'url'
import stripAnsi from 'strip-ansi'
import Concat from 'concat-with-sourcemaps'
import { test } from 'uvu'
import { is, equal, match, type } from 'uvu/assert'
import postcss, {
ProcessOptions,
@@ -44,32 +46,31 @@ function parseError(
throw new Error('Error was not thrown')
}
it('saves source', () => {
test('saves source', () => {
let error = parseError('a {\n content: "\n}')
expect(error instanceof CssSyntaxError).toBe(true)
expect(error.name).toBe('CssSyntaxError')
expect(error.message).toBe('<css input>:2:12: Unclosed string')
expect(error.reason).toBe('Unclosed string')
expect(error.line).toBe(2)
expect(error.column).toBe(12)
expect(error.source).toBe('a {\n content: "\n}')
is(error instanceof CssSyntaxError, true)
is(error.name, 'CssSyntaxError')
is(error.message, '<css input>:2:12: Unclosed string')
is(error.reason, 'Unclosed string')
is(error.line, 2)
is(error.column, 12)
is(error.source, 'a {\n content: "\n}')
expect(error.input).toEqual({
equal(error.input, {
line: error.line,
column: error.column,
source: error.source
})
})
it('has stack trace', () => {
expect(parseError('a {\n content: "\n}').stack).toMatch(
/css-syntax-error\.test\.ts/
)
test('has stack trace', () => {
match(parseError('a {\n content: "\n}').stack, /css-syntax-error\.test\.ts/)
})
it('highlights broken line with colors', () => {
expect(parseError('#a .b c() {').showSourceCode(true)).toEqual(
test('highlights broken line with colors', () => {
is(
parseError('#a .b c() {').showSourceCode(true),
pico.bold(pico.red('>')) +
pico.gray(' 1 | ') +
pico.magenta('#a') +
@@ -86,16 +87,16 @@ it('highlights broken line with colors', () => {
)
})
it('highlights broken line', () => {
expect(parseError('a {\n content: "\n}').showSourceCode(false)).toEqual(
test('highlights broken line', () => {
is(
parseError('a {\n content: "\n}').showSourceCode(false),
' 1 | a {\n' + '> 2 | content: "\n' + ' | ^\n' + ' 3 | }'
)
})
it('highlights broken line, when indented with tabs', () => {
expect(
parseError('a {\n\t \t content:\t"\n}').showSourceCode(false)
).toEqual(
test('highlights broken line, when indented with tabs', () => {
is(
parseError('a {\n\t \t content:\t"\n}').showSourceCode(false),
' 1 | a {\n' +
'> 2 | \t \t content:\t"\n' +
' | \t \t \t^\n' +
@@ -103,15 +104,14 @@ it('highlights broken line, when indented with tabs', () => {
)
})
it('highlights small code example', () => {
expect(parseError('a {').showSourceCode(false)).toEqual(
'> 1 | a {\n' + ' | ^'
)
test('highlights small code example', () => {
is(parseError('a {').showSourceCode(false), '> 1 | a {\n' + ' | ^')
})
it('add leading space for line numbers', () => {
test('add leading space for line numbers', () => {
let css = '\n\n\n\n\n\n\na {\n content: "\n}\n\n\n'
expect(parseError(css).showSourceCode(false)).toEqual(
is(
parseError(css).showSourceCode(false),
' 7 | \n' +
' 8 | a {\n' +
'> 9 | content: "\n' +
@@ -121,8 +121,9 @@ it('add leading space for line numbers', () => {
)
})
it('prints with highlight', () => {
expect(stripAnsi(parseError('a {').toString())).toEqual(
test('prints with highlight', () => {
is(
stripAnsi(parseError('a {').toString()),
'CssSyntaxError: <css input>:1:1: Unclosed block\n' +
'\n' +
'> 1 | a {\n' +
@@ -130,21 +131,19 @@ it('prints with highlight', () => {
)
})
it('misses highlights without source content', () => {
test('misses highlights without source content', () => {
let error = parseError('a {')
error.source = undefined
expect(error.toString()).toBe(
'CssSyntaxError: <css input>:1:1: Unclosed block'
)
is(error.toString(), 'CssSyntaxError: <css input>:1:1: Unclosed block')
})
it('misses position without source', () => {
test('misses position without source', () => {
let decl = postcss.decl({ prop: 'color', value: 'black' })
let error = decl.error('Test')
expect(error.toString()).toBe('CssSyntaxError: <css input>: Test')
is(error.toString(), 'CssSyntaxError: <css input>: Test')
})
it('uses source map', () => {
test('uses source map', () => {
function urlOf(file: string): string {
return pathToFileURL(join(__dirname, file)).toString()
}
@@ -158,11 +157,11 @@ it('uses source map', () => {
map: { prev: concat.sourceMap }
})
expect(error.file).toEqual(join(__dirname, 'b.css'))
expect(error.line).toBe(2)
expect(error.source).toBeUndefined()
is(error.file, join(__dirname, 'b.css'))
is(error.line, 2)
type(error.source, 'undefined')
expect(error.input).toEqual({
equal(error.input, {
url: urlOf(join('build', 'all.css')),
file: join(__dirname, 'build', 'all.css'),
line: 3,
@@ -171,7 +170,7 @@ it('uses source map', () => {
})
})
it('works with path in sources', () => {
test('works with path in sources', () => {
function pathOf(file: string): string {
return join(__dirname, file)
}
@@ -185,11 +184,11 @@ it('works with path in sources', () => {
map: { prev: concat.sourceMap }
})
expect(error.file).toEqual(join(__dirname, 'b.css'))
expect(error.line).toBe(2)
expect(error.source).toBeUndefined()
is(error.file, join(__dirname, 'b.css'))
is(error.line, 2)
type(error.source, 'undefined')
expect(error.input).toEqual({
equal(error.input, {
url: pathToFileURL(pathOf(join('build', 'all.css'))).toString(),
file: join(__dirname, 'build', 'all.css'),
line: 3,
@@ -198,7 +197,7 @@ it('works with path in sources', () => {
})
})
it('shows origin source', () => {
test('shows origin source', () => {
let input = postcss([() => {}]).process('a{}', {
from: '/a.css',
to: '/b.css',
@@ -208,10 +207,10 @@ it('shows origin source', () => {
from: '/b.css',
map: { prev: input.map }
})
expect(error.source).toBe('a{}')
is(error.source, 'a{}')
})
it('does not uses wrong source map', () => {
test('does not uses wrong source map', () => {
let error = parseError('a { }\nb {', {
from: 'build/all.css',
map: {
@@ -223,19 +222,17 @@ it('does not uses wrong source map', () => {
}
}
})
expect(error.file).toEqual(pathResolve('build/all.css'))
is(error.file, pathResolve('build/all.css'))
})
it('set source plugin', () => {
test('set source plugin', () => {
let a = postcss.parse('a{}').first as Rule
let error = a.error('Error', { plugin: 'PL' })
expect(error.plugin).toBe('PL')
expect(error.toString()).toMatch(
/^CssSyntaxError: PL: <css input>:1:1: Error/
)
is(error.plugin, 'PL')
match(error.toString(), /^CssSyntaxError: PL: <css input>:1:1: Error/)
})
it('set source plugin automatically', async () => {
test('set source plugin automatically', async () => {
let plugin: Plugin = {
postcssPlugin: 'test-plugin',
Once(css) {
@@ -248,11 +245,11 @@ it('set source plugin automatically', async () => {
let error = await catchError(() =>
postcss([plugin]).process('a{}', { from: undefined })
)
expect(error.plugin).toBe('test-plugin')
expect(error.toString()).toMatch(/test-plugin/)
is(error.plugin, 'test-plugin')
match(error.toString(), /test-plugin/)
})
it('set plugin automatically in async', async () => {
test('set plugin automatically in async', async () => {
let plugin: Plugin = {
postcssPlugin: 'async-plugin',
Once(css) {
@@ -267,5 +264,7 @@ it('set plugin automatically in async', async () => {
let error = await catchError(() =>
postcss([plugin]).process('a{}', { from: undefined })
)
expect(error.plugin).toBe('async-plugin')
is(error.plugin, 'async-plugin')
})
test.run()
Regular → Executable
+20 -15
View File
@@ -1,43 +1,48 @@
import { test } from 'uvu'
import { is } from 'uvu/assert'
import { Declaration, parse, Rule } from '../lib/postcss.js'
it('initializes with properties', () => {
test('initializes with properties', () => {
let decl = new Declaration({ prop: 'color', value: 'black' })
expect(decl.prop).toBe('color')
expect(decl.value).toBe('black')
is(decl.prop, 'color')
is(decl.value, 'black')
})
it('returns boolean important', () => {
test('returns boolean important', () => {
let decl = new Declaration({ prop: 'color', value: 'black' })
decl.important = true
expect(decl.toString()).toBe('color: black !important')
is(decl.toString(), 'color: black !important')
})
it('inserts default spaces', () => {
test('inserts default spaces', () => {
let decl = new Declaration({ prop: 'color', value: 'black' })
let rule = new Rule({ selector: 'a' })
rule.append(decl)
expect(rule.toString()).toBe('a {\n color: black\n}')
is(rule.toString(), 'a {\n color: black\n}')
})
it('clones spaces from another declaration', () => {
test('clones spaces from another declaration', () => {
let root = parse('a{color:black}')
let rule = root.first as Rule
let decl = new Declaration({ prop: 'margin', value: '0' })
rule.append(decl)
expect(root.toString()).toBe('a{color:black;margin:0}')
is(root.toString(), 'a{color:black;margin:0}')
})
it('converts value to string', () => {
test('converts value to string', () => {
// @ts-expect-error
let decl = new Declaration({ prop: 'color', value: 1 })
expect(decl.value).toBe('1')
is(decl.value, '1')
})
it('detects variable declarations', () => {
test('detects variable declarations', () => {
let prop = new Declaration({ prop: '--color', value: 'black' })
expect(prop.variable).toBe(true)
is(prop.variable, true)
let sass = new Declaration({ prop: '$color', value: 'black' })
expect(sass.variable).toBe(true)
is(sass.variable, true)
let decl = new Declaration({ prop: 'color', value: 'black' })
expect(decl.variable).toBe(false)
is(decl.variable, false)
})
test.run()
Regular → Executable
+11 -6
View File
@@ -1,7 +1,10 @@
import { test } from 'uvu'
import { is, match } from 'uvu/assert'
import { Result, parse } from '../lib/postcss.js'
import Document from '../lib/document.js'
it('generates result without map', () => {
test('generates result without map', () => {
let root = parse('a {}')
let document = new Document()
@@ -9,11 +12,11 @@ it('generates result without map', () => {
let result = document.toResult()
expect(result instanceof Result).toBe(true)
expect(result.css).toBe('a {}')
is(result instanceof Result, true)
is(result.css, 'a {}')
})
it('generates result with map', () => {
test('generates result with map', () => {
let root = parse('a {}')
let document = new Document()
@@ -21,6 +24,8 @@ it('generates result with map', () => {
let result = document.toResult({ map: true })
expect(result instanceof Result).toBe(true)
expect(result.css).toMatch(/a {}\n\/\*# sourceMappingURL=/)
is(result instanceof Result, true)
match(result.css, /a {}\n\/\*# sourceMappingURL=/)
})
test.run()
Regular → Executable
+17 -12
View File
@@ -1,8 +1,10 @@
import v8 from 'v8'
import { test } from 'uvu'
import { is, instance, throws } from 'uvu/assert'
import postcss, { Root, Declaration, Input, Rule } from '../lib/postcss.js'
it('rehydrates a JSON AST', () => {
test('rehydrates a JSON AST', () => {
let cssWithMap = postcss().process(
'.foo { color: red; font-size: 12pt; } /* abc */ @media (width: 60em) { }',
{
@@ -23,32 +25,35 @@ it('rehydrates a JSON AST', () => {
rehydrated.nodes[0].remove()
expect(rehydrated.nodes).toHaveLength(3)
is(rehydrated.nodes.length, 3)
expect(
is(
postcss().process(rehydrated, {
from: undefined,
map: {
inline: true
},
stringifier: postcss.stringify
}).css
).toBe(`/* abc */ @media (width: 60em) { }
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInguY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFzQyxRQUFRLEVBQUUsdUJBQXVCIiwiZmlsZSI6InRvLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi5mb28geyBjb2xvcjogcmVkOyBmb250LXNpemU6IDEycHQ7IH0gLyogYWJjICovIEBtZWRpYSAod2lkdGg6IDYwZW0pIHsgfSJdfQ== */`)
}).css,
`/* abc */ @media (width: 60em) { }
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInguY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFzQyxRQUFRLEVBQUUsdUJBQXVCIiwiZmlsZSI6InRvLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi5mb28geyBjb2xvcjogcmVkOyBmb250LXNpemU6IDEycHQ7IH0gLyogYWJjICovIEBtZWRpYSAod2lkdGg6IDYwZW0pIHsgfSJdfQ== */`
)
})
it('rehydrates an array of Nodes via JSON.stringify', () => {
test('rehydrates an array of Nodes via JSON.stringify', () => {
let root = postcss.parse('.cls { color: orange; }')
let rule = root.first as Rule
let json = JSON.stringify(rule.nodes)
let rehydrated = postcss.fromJSON(JSON.parse(json) as object[])
expect(rehydrated[0]).toBeInstanceOf(Declaration)
expect(rehydrated[0].source?.input).toBeInstanceOf(Input)
instance(rehydrated[0], Declaration)
instance(rehydrated[0].source?.input, Input)
})
it('throws when rehydrating an invalid JSON AST', () => {
expect(() => {
test('throws when rehydrating an invalid JSON AST', () => {
throws(() => {
postcss.fromJSON({ type: 'not-a-node-type' })
}).toThrow('Unknown node type: not-a-node-type')
}, 'Unknown node type: not-a-node-type')
})
test.run()
Regular → Executable
+30 -23
View File
@@ -1,66 +1,73 @@
import mozilla from 'source-map-js'
import { test } from 'uvu'
import { is, equal, type } from 'uvu/assert'
import LazyResult from '../lib/lazy-result.js'
import Processor from '../lib/processor.js'
let processor = new Processor()
it('contains AST', () => {
test('contains AST', () => {
let result = new LazyResult(processor, 'a {}', {})
expect(result.root.type).toBe('root')
is(result.root.type, 'root')
})
it('will stringify css', () => {
test('will stringify css', () => {
let result = new LazyResult(processor, 'a {}', {})
expect(result.css).toBe('a {}')
is(result.css, 'a {}')
})
it('stringifies css', () => {
test('stringifies css', () => {
let result = new LazyResult(processor, 'a {}', {})
expect(`${result}`).toEqual(result.css)
is(`${result}`, result.css)
})
it('has content alias for css', () => {
test('has content alias for css', () => {
let result = new LazyResult(processor, 'a {}', {})
expect(result.content).toBe('a {}')
is(result.content, 'a {}')
})
it('has map only if necessary', () => {
test('has map only if necessary', () => {
let result1 = new LazyResult(processor, '', {})
expect(result1.map).toBeUndefined()
type(result1.map, 'undefined')
let result2 = new LazyResult(processor, '', {})
expect(result2.map).toBeUndefined()
type(result2.map, 'undefined')
let result3 = new LazyResult(processor, '', { map: { inline: false } })
expect(result3.map instanceof mozilla.SourceMapGenerator).toBe(true)
is(result3.map instanceof mozilla.SourceMapGenerator, true)
})
it('contains options', () => {
test('contains options', () => {
let result = new LazyResult(processor, 'a {}', { to: 'a.css' })
expect(result.opts).toEqual({ to: 'a.css' })
equal(result.opts, { to: 'a.css' })
})
it('contains warnings', () => {
test('contains warnings', () => {
let result = new LazyResult(processor, 'a {}', {})
expect(result.warnings()).toEqual([])
equal(result.warnings(), [])
})
it('contains messages', () => {
test('contains messages', () => {
let result = new LazyResult(processor, 'a {}', {})
expect(result.messages).toEqual([])
equal(result.messages, [])
})
it('executes on finally callback', () => {
let mockCallback = jest.fn()
test('executes on finally callback', () => {
let callbackHaveBeenCalled = 0
let mockCallback = (): void => {
callbackHaveBeenCalled++
}
return new LazyResult(processor, 'a {}', {})
.finally(mockCallback)
.then(() => {
expect(mockCallback).toHaveBeenCalledTimes(1)
is(callbackHaveBeenCalled, 1)
})
})
it('prints its object type', () => {
test('prints its object type', () => {
let result = new LazyResult(processor, 'a {}', {})
expect(Object.prototype.toString.call(result)).toBe('[object LazyResult]')
is(Object.prototype.toString.call(result), '[object LazyResult]')
})
test.run()
Regular → Executable
+29 -24
View File
@@ -1,51 +1,56 @@
import { test } from 'uvu'
import { equal } from 'uvu/assert'
import { list } from '../lib/postcss.js'
it('space() splits list by spaces', () => {
expect(list.space('a b')).toEqual(['a', 'b'])
test('space() splits list by spaces', () => {
equal(list.space('a b'), ['a', 'b'])
})
it('space() trims values', () => {
expect(list.space(' a b ')).toEqual(['a', 'b'])
test('space() trims values', () => {
equal(list.space(' a b '), ['a', 'b'])
})
it('space() checks quotes', () => {
expect(list.space('"a b\\"" \'\'')).toEqual(['"a b\\""', "''"])
test('space() checks quotes', () => {
equal(list.space('"a b\\"" \'\''), ['"a b\\""', "''"])
})
it('space() checks functions', () => {
expect(list.space('f( )) a( () )')).toEqual(['f( ))', 'a( () )'])
test('space() checks functions', () => {
equal(list.space('f( )) a( () )'), ['f( ))', 'a( () )'])
})
it('space() does not split on escaped spaces', () => {
expect(list.space('a\\ b')).toEqual(['a\\ b'])
test('space() does not split on escaped spaces', () => {
equal(list.space('a\\ b'), ['a\\ b'])
})
it('space() works from variable', () => {
test('space() works from variable', () => {
let space = list.space
expect(space('a b')).toEqual(['a', 'b'])
equal(space('a b'), ['a', 'b'])
})
it('comma() splits list by spaces', () => {
expect(list.comma('a, b')).toEqual(['a', 'b'])
test('comma() splits list by spaces', () => {
equal(list.comma('a, b'), ['a', 'b'])
})
it('comma() adds last empty', () => {
expect(list.comma('a, b,')).toEqual(['a', 'b', ''])
test('comma() adds last empty', () => {
equal(list.comma('a, b,'), ['a', 'b', ''])
})
it('comma() checks quotes', () => {
expect(list.comma('"a,b\\"", \'\'')).toEqual(['"a,b\\""', "''"])
test('comma() checks quotes', () => {
equal(list.comma('"a,b\\"", \'\''), ['"a,b\\""', "''"])
})
it('comma() checks functions', () => {
expect(list.comma('f(,)), a(,(),)')).toEqual(['f(,))', 'a(,(),)'])
test('comma() checks functions', () => {
equal(list.comma('f(,)), a(,(),)'), ['f(,))', 'a(,(),)'])
})
it('comma() does not split on escaped commas', () => {
expect(list.comma('a\\, b')).toEqual(['a\\, b'])
test('comma() does not split on escaped commas', () => {
equal(list.comma('a\\, b'), ['a\\, b'])
})
it('comma() works from variable', () => {
test('comma() works from variable', () => {
let comma = list.comma
expect(comma('a, b')).toEqual(['a', 'b'])
equal(comma('a, b'), ['a', 'b'])
})
test.run()
Regular → Executable
+103 -98
View File
@@ -2,6 +2,8 @@ import { SourceMapConsumer, SourceMapGenerator } from 'source-map-js'
import { removeSync, outputFileSync } from 'fs-extra'
import { join, resolve, parse } from 'path'
import { existsSync } from 'fs'
import { test } from 'uvu'
import { is, type, equal, match } from 'uvu/assert'
import postcss, { SourceMap, Rule, Root } from '../lib/postcss.js'
import PreviousMap from '../lib/previous-map.js'
@@ -28,22 +30,22 @@ let lighter = postcss((css: Root) => {
})
})
afterEach(() => {
test.after.each(() => {
if (existsSync(dir)) removeSync(dir)
})
it('adds map field only on request', () => {
expect(postcss([() => {}]).process('a {}').map).toBeUndefined()
test('adds map field only on request', () => {
type(postcss([() => {}]).process('a {}').map, 'undefined')
})
it('return map generator', () => {
test('return map generator', () => {
let map = postcss([() => {}]).process('a {}', {
map: { inline: false }
}).map
expect(map instanceof SourceMapGenerator).toBe(true)
is(map instanceof SourceMapGenerator, true)
})
it('generate right source map', () => {
test('generate right source map', () => {
let css = 'a {\n color: black;\n }'
let processor = postcss((root: Root) => {
root.walkRules(rule => {
@@ -61,39 +63,39 @@ it('generate right source map', () => {
})
let map = read(result)
expect(map.file).toBe('b.css')
is(map.file, 'b.css')
expect(map.originalPositionFor({ line: 1, column: 0 })).toEqual({
equal(map.originalPositionFor({ line: 1, column: 0 }), {
source: 'a.css',
line: 1,
column: 0,
name: null
})
expect(map.originalPositionFor({ line: 1, column: 2 })).toEqual({
equal(map.originalPositionFor({ line: 1, column: 2 }), {
source: 'a.css',
line: 1,
column: 0,
name: null
})
expect(map.originalPositionFor({ line: 2, column: 2 })).toEqual({
equal(map.originalPositionFor({ line: 2, column: 2 }), {
source: 'a.css',
line: 2,
column: 2,
name: null
})
expect(map.originalPositionFor({ line: 3, column: 2 })).toEqual({
equal(map.originalPositionFor({ line: 3, column: 2 }), {
source: 'a.css',
line: 2,
column: 2,
name: null
})
expect(map.originalPositionFor({ line: 3, column: 14 })).toEqual({
equal(map.originalPositionFor({ line: 3, column: 14 }), {
source: 'a.css',
line: 2,
column: 14,
name: null
})
expect(map.originalPositionFor({ line: 4, column: 2 })).toEqual({
equal(map.originalPositionFor({ line: 4, column: 2 }), {
source: 'a.css',
line: 3,
column: 2,
@@ -101,7 +103,7 @@ it('generate right source map', () => {
})
})
it('changes previous source map', () => {
test('changes previous source map', () => {
let css = 'a { color: black }'
let doubled = doubler.process(css, {
@@ -117,7 +119,7 @@ it('changes previous source map', () => {
})
let map = consumer(lighted.map)
expect(map.originalPositionFor({ line: 1, column: 18 })).toEqual({
equal(map.originalPositionFor({ line: 1, column: 18 }), {
source: 'a.css',
line: 1,
column: 4,
@@ -125,7 +127,7 @@ it('changes previous source map', () => {
})
})
it('adds source map annotation', () => {
test('adds source map annotation', () => {
let css = 'a { }/*# sourceMappingURL=a.css.map */'
let result = postcss([() => {}]).process(css, {
from: 'a.css',
@@ -133,10 +135,10 @@ it('adds source map annotation', () => {
map: { inline: false }
})
expect(result.css).toBe('a { }\n/*# sourceMappingURL=b.css.map */')
is(result.css, 'a { }\n/*# sourceMappingURL=b.css.map */')
})
it('misses source map annotation, if user ask', () => {
test('misses source map annotation, if user ask', () => {
let css = 'a { }'
let result = postcss([() => {}]).process(css, {
from: 'a.css',
@@ -144,10 +146,10 @@ it('misses source map annotation, if user ask', () => {
map: { annotation: false }
})
expect(result.css).toEqual(css)
is(result.css, css)
})
it('misses source map annotation, if previous map missed it', () => {
test('misses source map annotation, if previous map missed it', () => {
let css = 'a { }'
let step1 = postcss([() => {}]).process(css, {
@@ -162,26 +164,27 @@ it('misses source map annotation, if previous map missed it', () => {
map: { prev: step1.map }
})
expect(step2.css).toEqual(css)
is(step2.css, css)
})
it('uses user path in annotation, relative to options.to', () => {
test('uses user path in annotation, relative to options.to', () => {
let result = postcss([() => {}]).process('a { }', {
from: 'source/a.css',
to: 'build/b.css',
map: { annotation: 'maps/b.map' }
})
expect(result.css).toBe('a { }\n/*# sourceMappingURL=maps/b.map */')
is(result.css, 'a { }\n/*# sourceMappingURL=maps/b.map */')
let map = consumer(result.map)
expect(map.file).toEqual(join('..', 'b.css'))
expect(map.originalPositionFor({ line: 1, column: 0 }).source).toBe(
is(map.file, join('..', 'b.css'))
is(
map.originalPositionFor({ line: 1, column: 0 }).source,
'../../source/a.css'
)
})
it('generates inline map', () => {
test('generates inline map', () => {
let css = 'a { }'
let inline = postcss([() => {}]).process(css, {
@@ -190,8 +193,8 @@ it('generates inline map', () => {
map: { inline: true }
})
expect(inline.map).toBeUndefined()
expect(inline.css).toMatch(/# sourceMappingURL=data:/)
type(inline.map, 'undefined')
match(inline.css, /# sourceMappingURL=data:/)
let separated = postcss([() => {}]).process(css, {
from: 'a.css',
@@ -201,19 +204,19 @@ it('generates inline map', () => {
let base64 = Buffer.from(separated.map.toString()).toString('base64')
let end = inline.css.slice(-base64.length - 3)
expect(end).toEqual(base64 + ' */')
is(end, base64 + ' */')
})
it('generates inline map by default', () => {
test('generates inline map by default', () => {
let inline = postcss([() => {}]).process('a { }', {
from: 'a.css',
to: 'b.css',
map: true
})
expect(inline.css).toMatch(/# sourceMappingURL=data:/)
match(inline.css, /# sourceMappingURL=data:/)
})
it('generates separated map if previous map was not inlined', () => {
test('generates separated map if previous map was not inlined', () => {
let step1 = doubler.process('a { color: black }', {
from: 'a.css',
to: 'b.css',
@@ -225,20 +228,20 @@ it('generates separated map if previous map was not inlined', () => {
map: { prev: step1.map }
})
expect(typeof step2.map).toBe('object')
type(step2.map, 'object')
})
it('generates separated map on annotation option', () => {
test('generates separated map on annotation option', () => {
let result = postcss([() => {}]).process('a { }', {
from: 'a.css',
to: 'b.css',
map: { annotation: false }
})
expect(typeof result.map).toBe('object')
type(result.map, 'object')
})
it('allows change map type', () => {
test('allows change map type', () => {
let step1 = postcss([() => {}]).process('a { }', {
from: 'a.css',
to: 'b.css',
@@ -251,11 +254,11 @@ it('allows change map type', () => {
map: { inline: false }
})
expect(typeof step2.map).toBe('object')
expect(step2.css).toMatch(/# sourceMappingURL=c\.css\.map/)
type(step2.map, 'object')
match(step2.css, /# sourceMappingURL=c\.css\.map/)
})
it('misses check files on requires', () => {
test('misses check files on requires', () => {
let file = join(dir, 'a.css')
let step1 = doubler.process('a { }', {
@@ -271,24 +274,24 @@ it('misses check files on requires', () => {
map: false
})
expect(step2.map).toBeUndefined()
type(step2.map, 'undefined')
})
it('works in subdirs', () => {
test('works in subdirs', () => {
let result = doubler.process('a { }', {
from: 'from/a.css',
to: 'out/b.css',
map: { inline: false }
})
expect(result.css).toMatch(/sourceMappingURL=b.css.map/)
match(result.css, /sourceMappingURL=b.css.map/)
let map = consumer(result.map)
expect(map.file).toBe('b.css')
expect(map.sources).toEqual(['../from/a.css'])
is(map.file, 'b.css')
equal(map.sources, ['../from/a.css'])
})
it('uses map from subdir', () => {
test('uses map from subdir', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
@@ -305,7 +308,7 @@ it('uses map from subdir', () => {
line: 1,
column: 0
}).source
expect(source).toBe('../../a.css')
is(source, '../../a.css')
let step3 = doubler.process(step2.css, {
from: 'c.css',
@@ -317,10 +320,10 @@ it('uses map from subdir', () => {
line: 1,
column: 0
}).source
expect(source).toBe('../../a.css')
is(source, '../../a.css')
})
it('uses map from subdir if it inlined', () => {
test('uses map from subdir if it inlined', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
@@ -337,10 +340,10 @@ it('uses map from subdir if it inlined', () => {
line: 1,
column: 0
}).source
expect(source).toBe('../../a.css')
is(source, '../../a.css')
})
it('uses map from subdir if it written as a file', () => {
test('uses map from subdir if it written as a file', () => {
let step1 = doubler.process('a { }', {
from: 'source/a.css',
to: 'one/b.css',
@@ -351,7 +354,7 @@ it('uses map from subdir if it written as a file', () => {
line: 1,
column: 0
}).source
expect(source).toBe('../../source/a.css')
is(source, '../../source/a.css')
let file = join(dir, 'one', 'maps', 'b.css.map')
outputFileSync(file, step1.map.toString())
@@ -366,10 +369,10 @@ it('uses map from subdir if it written as a file', () => {
line: 1,
column: 0
}).source
expect(source).toBe('../source/a.css')
is(source, '../source/a.css')
})
it('works with different types of maps', () => {
test('works with different types of maps', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'b.css',
@@ -389,31 +392,31 @@ it('works with different types of maps', () => {
line: 1,
column: 0
}).source
expect(source).toBe('a.css')
is(source, 'a.css')
}
})
it('sets source content by default', () => {
test('sets source content by default', () => {
let result = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: true
})
expect(read(result).sourceContentFor('../a.css')).toBe('a { }')
is(read(result).sourceContentFor('../a.css'), 'a { }')
})
it('misses source content on request', () => {
test('misses source content on request', () => {
let result = doubler.process('a { }', {
from: 'a.css',
to: 'out/b.css',
map: { sourcesContent: false }
})
expect(read(result).sourceContentFor('../a.css')).toBeNull()
is(read(result).sourceContentFor('../a.css'), null)
})
it('misses source content if previous not have', () => {
test('misses source content if previous not have', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/a.css',
@@ -429,10 +432,10 @@ it('misses source content if previous not have', () => {
if (file1.first) file2.append(file1.first.clone())
let step2 = file2.toResult({ to: 'c.css', map: true })
expect(read(step2).sourceContentFor('b.css')).toBeNull()
is(read(step2).sourceContentFor('b.css'), null)
})
it('misses source content on request in multiple steps', () => {
test('misses source content on request in multiple steps', () => {
let step1 = doubler.process('a { }', {
from: 'a.css',
to: 'out/a.css',
@@ -452,23 +455,23 @@ it('misses source content on request in multiple steps', () => {
})
let map = read(step2)
expect(map.sourceContentFor('b.css')).toBeNull()
expect(map.sourceContentFor('../a.css')).toBeNull()
is(map.sourceContentFor('b.css'), null)
is(map.sourceContentFor('../a.css'), null)
})
it('detects input file name from map', () => {
test('detects input file name from map', () => {
let one = doubler.process('a { }', { to: 'a.css', map: true })
let two = doubler.process(one.css, { map: { prev: one.map } })
expect(two.root.first?.source?.input.file).toEqual(resolve('a.css'))
is(two.root.first?.source?.input.file, resolve('a.css'))
})
it('works without file names', () => {
test('works without file names', () => {
let step1 = doubler.process('a { }', { map: true })
let step2 = doubler.process(step1.css)
expect(step2.css).toMatch(/a { }\n\/\*/)
match(step2.css, /a { }\n\/\*/)
})
it('supports UTF-8', () => {
test('supports UTF-8', () => {
let step1 = doubler.process('a { }', {
from: 'вход.css',
to: 'шаг1.css',
@@ -479,10 +482,10 @@ it('supports UTF-8', () => {
to: 'выход.css'
})
expect(read(step2).file).toBe('выход.css')
is(read(step2).file, 'выход.css')
})
it('generates map for node created manually', () => {
test('generates map for node created manually', () => {
let contenter = postcss((css: Root) => {
if (css.first && css.first.type === 'rule') {
css.first.prepend({ selector: 'b' })
@@ -490,7 +493,7 @@ it('generates map for node created manually', () => {
})
let result = contenter.process('a:after{\n}', { map: true })
let map = read(result)
expect(map.originalPositionFor({ line: 2, column: 5 })).toEqual({
equal(map.originalPositionFor({ line: 2, column: 5 }), {
source: '<no source>',
column: 0,
line: 1,
@@ -498,20 +501,20 @@ it('generates map for node created manually', () => {
})
})
it('uses input file name as output file name', () => {
test('uses input file name as output file name', () => {
let result = doubler.process('a{}', {
from: 'a.css',
map: { inline: false }
})
expect(result.map.toJSON().file).toBe('a.css')
is(result.map.toJSON().file, 'a.css')
})
it('uses to.css as default output name', () => {
test('uses to.css as default output name', () => {
let result = doubler.process('a{}', { map: { inline: false } })
expect(result.map.toJSON().file).toBe('to.css')
is(result.map.toJSON().file, 'to.css')
})
it('supports annotation comment in any place', () => {
test('supports annotation comment in any place', () => {
let css = '/*# sourceMappingURL=a.css.map */a { }'
let result = postcss([() => {}]).process(css, {
from: 'a.css',
@@ -519,10 +522,10 @@ it('supports annotation comment in any place', () => {
map: { inline: false }
})
expect(result.css).toBe('a { }\n/*# sourceMappingURL=b.css.map */')
is(result.css, 'a { }\n/*# sourceMappingURL=b.css.map */')
})
it('does not update annotation on request', () => {
test('does not update annotation on request', () => {
let css = 'a { }/*# sourceMappingURL=a.css.map */'
let result = postcss([() => {}]).process(css, {
from: 'a.css',
@@ -530,10 +533,10 @@ it('does not update annotation on request', () => {
map: { annotation: false, inline: false }
})
expect(result.css).toBe('a { }/*# sourceMappingURL=a.css.map */')
is(result.css, 'a { }/*# sourceMappingURL=a.css.map */')
})
it('clears source map', () => {
test('clears source map', () => {
let css1 = postcss.root().toResult({ map: true }).css
let css2 = postcss.root().toResult({ map: true }).css
@@ -542,43 +545,43 @@ it('clears source map', () => {
root.append(css2)
let css = root.toResult({ map: true }).css
expect(css.match(/sourceMappingURL/g)).toHaveLength(1)
is(css.match(/sourceMappingURL/g)?.length, 1)
})
it('uses Windows line separation too', () => {
test('uses Windows line separation too', () => {
let result = postcss([() => {}]).process('a {\r\n}', { map: true })
expect(result.css).toMatch(/a {\r\n}\r\n\/\*# sourceMappingURL=/)
match(result.css, /a {\r\n}\r\n\/\*# sourceMappingURL=/)
})
it('`map.from` should override the source map sources', () => {
test('`map.from` should override the source map sources', () => {
let result = postcss([() => {}]).process('a{}', {
map: {
inline: false,
from: 'file:///dir/a.css'
}
})
expect(result.map.toJSON().sources).toEqual(['file:///dir/a.css'])
equal(result.map.toJSON().sources, ['file:///dir/a.css'])
})
it('preserves absolute urls in `to`', () => {
test('preserves absolute urls in `to`', () => {
let result = postcss([() => {}]).process('a{}', {
from: '/dir/to/a.css',
to: 'http://example.com/a.css',
map: { inline: false }
})
expect(result.map.toJSON().file).toBe('http://example.com/a.css')
is(result.map.toJSON().file, 'http://example.com/a.css')
})
it('preserves absolute urls in sources', () => {
test('preserves absolute urls in sources', () => {
let result = postcss([() => {}]).process('a{}', {
from: 'file:///dir/a.css',
to: 'http://example.com/a.css',
map: { inline: false }
})
expect(result.map.toJSON().sources).toEqual(['file:///dir/a.css'])
equal(result.map.toJSON().sources, ['file:///dir/a.css'])
})
it('uses absolute path on request', () => {
test('uses absolute path on request', () => {
let result = postcss([() => {}]).process('a{}', {
from: '/dir/a.css',
to: '/dir/b.css',
@@ -588,10 +591,10 @@ it('uses absolute path on request', () => {
if (process.platform === 'win32') {
root = '/' + parse(process.cwd()).root.replace(/\\/g, '/')
}
expect(result.map.toJSON().sources).toEqual([`file://${root}dir/a.css`])
equal(result.map.toJSON().sources, [`file://${root}dir/a.css`])
})
it('preserves absolute urls in sources from previous map', () => {
test('preserves absolute urls in sources from previous map', () => {
let result1 = postcss([() => {}]).process('a{}', {
from: 'http://example.com/a.css',
to: 'http://example.com/b.css',
@@ -603,11 +606,11 @@ it('preserves absolute urls in sources from previous map', () => {
inline: false
}
})
expect(result2.root.source?.input.file).toBe('http://example.com/b.css')
expect(result2.map.toJSON().sources).toEqual(['http://example.com/a.css'])
is(result2.root.source?.input.file, 'http://example.com/b.css')
equal(result2.map.toJSON().sources, ['http://example.com/a.css'])
})
it('allows dynamic annotations', () => {
test('allows dynamic annotations', () => {
let result = postcss([() => {}]).process('a{}', {
to: 'out.css',
map: {
@@ -617,14 +620,16 @@ it('allows dynamic annotations', () => {
}
}
})
expect(result.css).toBe('a{}\n/*# sourceMappingURL=out.css-a.map */')
is(result.css, 'a{}\n/*# sourceMappingURL=out.css-a.map */')
})
it('uses URLs in sources', () => {
test('uses URLs in sources', () => {
let result = postcss([() => {}]).process('a{}', {
from: 'a b.css',
to: 'dir/b.css',
map: { inline: false }
})
expect(result.map.toJSON().sources).toEqual(['../a%20b.css'])
equal(result.map.toJSON().sources, ['../a%20b.css'])
})
test.run()
Regular → Executable
+123 -119
View File
@@ -1,4 +1,6 @@
import { resolve } from 'path'
import { test } from 'uvu'
import { is, equal, type, not } from 'uvu/assert'
import postcss, {
AnyNode,
@@ -19,52 +21,52 @@ function stringify(node: AnyNode, builder: (str: string) => void): void {
}
}
it('error() generates custom error', () => {
test('error() generates custom error', () => {
let file = resolve('a.css')
let css = parse('a{}', { from: file })
let a = css.first as Rule
let error = a.error('Test')
expect(error instanceof CssSyntaxError).toBe(true)
expect(error.message).toEqual(file + ':1:1: Test')
is(error instanceof CssSyntaxError, true)
is(error.message, file + ':1:1: Test')
})
it('error() generates custom error for nodes without source', () => {
test('error() generates custom error for nodes without source', () => {
let rule = new Rule({ selector: 'a' })
let error = rule.error('Test')
expect(error.message).toBe('<css input>: Test')
is(error.message, '<css input>: Test')
})
it('error() highlights index', () => {
test('error() highlights index', () => {
let root = parse('a { b: c }')
let a = root.first as Rule
let b = a.first as Declaration
let error = b.error('Bad semicolon', { index: 1 })
expect(error.showSourceCode(false)).toEqual(
'> 1 | a { b: c }\n' + ' | ^'
)
is(error.showSourceCode(false), '> 1 | a { b: c }\n' + ' | ^')
})
it('error() highlights word', () => {
test('error() highlights word', () => {
let root = parse('a { color: x red }')
let a = root.first as Rule
let color = a.first as Declaration
let error = color.error('Wrong color', { word: 'x' })
expect(error.showSourceCode(false)).toEqual(
is(
error.showSourceCode(false),
'> 1 | a { color: x red }\n' + ' | ^'
)
})
it('error() highlights word in multiline string', () => {
test('error() highlights word in multiline string', () => {
let root = parse('a { color: red\n x }')
let a = root.first as Rule
let color = a.first as Declaration
let error = color.error('Wrong color', { word: 'x' })
expect(error.showSourceCode(false)).toEqual(
is(
error.showSourceCode(false),
' 1 | a { color: red\n' + '> 2 | x }\n' + ' | ^'
)
})
it('warn() attaches a warning to the result object', async () => {
test('warn() attaches a warning to the result object', async () => {
let warning: any
let warner: Plugin = {
postcssPlugin: 'warner',
@@ -74,34 +76,34 @@ it('warn() attaches a warning to the result object', async () => {
}
let result = await postcss([warner]).process('a{}', { from: undefined })
expect(warning.type).toBe('warning')
expect(warning.text).toBe('FIRST!')
expect(warning.plugin).toBe('warner')
expect(result.warnings()).toEqual([warning])
is(warning.type, 'warning')
is(warning.text, 'FIRST!')
is(warning.plugin, 'warner')
equal(result.warnings(), [warning])
})
it('warn() accepts options', () => {
test('warn() accepts options', () => {
let warner = (css: Root, result: Result): void => {
css.first?.warn(result, 'FIRST!', { index: 1 })
}
let result = postcss([warner]).process('a{}')
expect(result.warnings()).toHaveLength(1)
is(result.warnings().length, 1)
let warning = result.warnings()[0] as any
expect(warning.index).toBe(1)
is(warning.index, 1)
})
it('remove() removes node from parent', () => {
test('remove() removes node from parent', () => {
let rule = new Rule({ selector: 'a' })
let decl = new Declaration({ prop: 'color', value: 'black' })
rule.append(decl)
decl.remove()
expect(rule.nodes).toHaveLength(0)
expect(decl.parent).toBeUndefined()
is(rule.nodes.length, 0)
type(decl.parent, 'undefined')
})
it('replaceWith() inserts new node', () => {
test('replaceWith() inserts new node', () => {
let rule = new Rule({ selector: 'a' })
rule.append({ prop: 'color', value: 'black' })
rule.append({ prop: 'width', value: '1px' })
@@ -111,8 +113,9 @@ it('replaceWith() inserts new node', () => {
let width = rule.nodes[1]
let result = width.replaceWith(node)
expect(result).toEqual(width)
expect(rule.toString()).toEqual(
equal(result, width)
is(
rule.toString(),
'a {\n' +
' color: black;\n' +
' min-width: 1px;\n' +
@@ -121,7 +124,7 @@ it('replaceWith() inserts new node', () => {
)
})
it('replaceWith() inserts new root', () => {
test('replaceWith() inserts new root', () => {
let root = new Root()
root.append(new AtRule({ name: 'import', params: '"a.css"' }))
@@ -130,21 +133,21 @@ it('replaceWith() inserts new root', () => {
a.append(new Rule({ selector: 'b' }))
root.first?.replaceWith(a)
expect(root.toString()).toBe('a {}\nb {}')
is(root.toString(), 'a {}\nb {}')
})
it('replaceWith() replaces node', () => {
test('replaceWith() replaces node', () => {
let css = parse('a{one:1;two:2}')
let a = css.first as Rule
let one = a.first as Declaration
let result = one.replaceWith({ prop: 'fix', value: 'fixed' })
expect(result.prop).toBe('one')
expect(result.parent).toBeUndefined()
expect(css.toString()).toBe('a{fix:fixed;two:2}')
is(result.prop, 'one')
type(result.parent, 'undefined')
is(css.toString(), 'a{fix:fixed;two:2}')
})
it('replaceWith() can include itself', () => {
test('replaceWith() can include itself', () => {
let css = parse('a{one:1;two:2}')
let a = css.first as Rule
let one = a.first as Declaration
@@ -152,56 +155,56 @@ it('replaceWith() can include itself', () => {
let afterDecl = { prop: 'fix2', value: 'fixedTwo' }
one.replaceWith(beforeDecl, one, afterDecl)
expect(css.toString()).toBe('a{fix1:fixedOne;one:1;fix2:fixedTwo;two:2}')
is(css.toString(), 'a{fix1:fixedOne;one:1;fix2:fixedTwo;two:2}')
})
it('toString() accepts custom stringifier', () => {
expect(new Rule({ selector: 'a' }).toString(stringify)).toBe('a')
test('toString() accepts custom stringifier', () => {
is(new Rule({ selector: 'a' }).toString(stringify), 'a')
})
it('toString() accepts custom syntax', () => {
expect(new Rule({ selector: 'a' }).toString({ stringify })).toBe('a')
test('toString() accepts custom syntax', () => {
is(new Rule({ selector: 'a' }).toString({ stringify }), 'a')
})
it('assign() assigns to node', () => {
test('assign() assigns to node', () => {
let decl = new Declaration({ prop: 'white-space', value: 'overflow-wrap' })
expect(decl.prop).toBe('white-space')
expect(decl.value).toBe('overflow-wrap')
is(decl.prop, 'white-space')
is(decl.value, 'overflow-wrap')
decl.assign({ prop: 'word-wrap', value: 'break-word' })
expect(decl.prop).toBe('word-wrap')
expect(decl.value).toBe('break-word')
is(decl.prop, 'word-wrap')
is(decl.value, 'break-word')
})
it('clone() clones nodes', () => {
test('clone() clones nodes', () => {
let rule = new Rule({ selector: 'a' })
rule.append({ prop: 'color', value: '/**/black' })
let clone = rule.clone()
expect(clone.parent).toBeUndefined()
type(clone.parent, 'undefined')
expect(rule.first?.parent).toBe(rule)
expect(clone.first?.parent).toBe(clone)
equal(rule.first?.parent, rule)
equal(clone.first?.parent, clone)
clone.append({ prop: 'z-index', value: '1' })
expect(rule.nodes).toHaveLength(1)
is(rule.nodes.length, 1)
})
it('clone() overrides properties', () => {
test('clone() overrides properties', () => {
let rule = new Rule({ selector: 'a' })
let clone = rule.clone({ selector: 'b' })
expect(clone.selector).toBe('b')
is(clone.selector, 'b')
})
it('clone() keeps code style', () => {
test('clone() keeps code style', () => {
let css = parse('@page 1{a{color:black;}}')
expect(css.clone().toString()).toBe('@page 1{a{color:black;}}')
is(css.clone().toString(), '@page 1{a{color:black;}}')
})
it('clone() works with null in raws', () => {
test('clone() works with null in raws', () => {
let decl = new Declaration({
prop: 'color',
value: 'black',
@@ -209,89 +212,90 @@ it('clone() works with null in raws', () => {
raws: { value: null }
})
let clone = decl.clone()
expect(Object.keys(clone.raws)).toEqual(['value'])
equal(Object.keys(clone.raws), ['value'])
})
it('cloneBefore() clones and insert before current node', () => {
test('cloneBefore() clones and insert before current node', () => {
let rule = new Rule({ selector: 'a', raws: { after: '' } })
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } })
let result = rule.first?.cloneBefore({ value: '2' })
expect(result).toBe(rule.first)
expect(rule.toString()).toBe('a {z-index: 2;z-index: 1}')
equal(result, rule.first)
is(rule.toString(), 'a {z-index: 2;z-index: 1}')
})
it('cloneAfter() clones and insert after current node', () => {
test('cloneAfter() clones and insert after current node', () => {
let rule = new Rule({ selector: 'a', raws: { after: '' } })
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } })
let result = rule.first?.cloneAfter({ value: '2' })
expect(result).toBe(rule.last)
expect(rule.toString()).toBe('a {z-index: 1;z-index: 2}')
equal(result, rule.last)
is(rule.toString(), 'a {z-index: 1;z-index: 2}')
})
it('before() insert before current node', () => {
test('before() insert before current node', () => {
let rule = new Rule({ selector: 'a', raws: { after: '' } })
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } })
let result = rule.first?.before('color: black')
expect(result).toBe(rule.last)
expect(rule.toString()).toBe('a {color: black;z-index: 1}')
equal(result, rule.last)
is(rule.toString(), 'a {color: black;z-index: 1}')
})
it('after() insert after current node', () => {
test('after() insert after current node', () => {
let rule = new Rule({ selector: 'a', raws: { after: '' } })
rule.append({ prop: 'z-index', value: '1', raws: { before: '' } })
let result = rule.first?.after('color: black')
expect(result).toBe(rule.first)
expect(rule.toString()).toBe('a {z-index: 1;color: black}')
equal(result, rule.first)
is(rule.toString(), 'a {z-index: 1;color: black}')
})
it('next() returns next node', () => {
test('next() returns next node', () => {
let css = parse('a{one:1;two:2}')
let a = css.first as Rule
expect(a.first?.next()).toBe(a.last)
expect(a.last?.next()).toBeUndefined()
equal(a.first?.next(), a.last)
type(a.last?.next(), 'undefined')
})
it('next() returns undefined on no parent', () => {
test('next() returns undefined on no parent', () => {
let css = parse('')
expect(css.next()).toBeUndefined()
type(css.next(), 'undefined')
})
it('prev() returns previous node', () => {
test('prev() returns previous node', () => {
let css = parse('a{one:1;two:2}')
let a = css.first as Rule
expect(a.last?.prev()).toBe(a.first)
expect(a.first?.prev()).toBeUndefined()
equal(a.last?.prev(), a.first)
type(a.first?.prev(), 'undefined')
})
it('prev() returns undefined on no parent', () => {
test('prev() returns undefined on no parent', () => {
let css = parse('')
expect(css.prev()).toBeUndefined()
type(css.prev(), 'undefined')
})
it('toJSON() cleans parents inside', () => {
test('toJSON() cleans parents inside', () => {
let rule = new Rule({ selector: 'a' })
rule.append({ prop: 'color', value: 'b' })
let json = rule.toJSON() as any
expect(json.parent).toBeUndefined()
expect(json.nodes[0].parent).toBeUndefined()
type(json.parent, 'undefined')
type(json.nodes[0].parent, 'undefined')
expect(JSON.stringify(rule)).toEqual(
is(
JSON.stringify(rule),
'{"raws":{},"selector":"a","type":"rule","nodes":[' +
'{"raws":{},"prop":"color","value":"b","type":"decl"}' +
'],"inputs":[]}'
)
})
it('toJSON() converts custom properties', () => {
test('toJSON() converts custom properties', () => {
let root = new Root() as any
root._cache = [1]
root._hack = {
@@ -300,7 +304,7 @@ it('toJSON() converts custom properties', () => {
}
}
expect(root.toJSON()).toEqual({
equal(root.toJSON(), {
type: 'root',
nodes: [],
raws: {},
@@ -310,102 +314,102 @@ it('toJSON() converts custom properties', () => {
})
})
it('raw() has shortcut to stringifier', () => {
test('raw() has shortcut to stringifier', () => {
let rule = new Rule({ selector: 'a' })
expect(rule.raw('before')).toBe('')
is(rule.raw('before'), '')
})
it('root() returns root', () => {
test('root() returns root', () => {
let css = parse('@page{a{color:black}}')
let page = css.first as AtRule
let a = page.first as Rule
let color = a.first as Declaration
expect(color.root()).toBe(css)
equal(color.root(), css)
})
it('root() returns parent of parents', () => {
test('root() returns parent of parents', () => {
let rule = new Rule({ selector: 'a' })
rule.append({ prop: 'color', value: 'black' })
expect(rule.first?.root()).toBe(rule)
equal(rule.first?.root(), rule)
})
it('root() returns self on root', () => {
test('root() returns self on root', () => {
let rule = new Rule({ selector: 'a' })
expect(rule.root()).toBe(rule)
equal(rule.root(), rule)
})
it('root() returns root in document', () => {
test('root() returns root in document', () => {
let css = new Document({ nodes: [parse('@page{a{color:black}}')] })
let root = css.first as Root
let page = root.first as AtRule
let a = page.first as Rule
let color = a.first as Declaration
expect(color.root()).toBe(root)
equal(color.root(), root)
})
it('root() on root in document returns same root', () => {
test('root() on root in document returns same root', () => {
let document = new Document()
let root = new Root()
document.append(root)
expect(document.first?.root()).toBe(root)
equal(document.first?.root(), root)
})
it('root() returns self on document', () => {
test('root() returns self on document', () => {
let document = new Document()
expect(document.root()).toBe(document)
equal(document.root(), document)
})
it('cleanRaws() cleans style recursivelly', () => {
test('cleanRaws() cleans style recursivelly', () => {
let css = parse('@page{a{color:black}}')
css.cleanRaws()
expect(css.toString()).toBe(
'@page {\n a {\n color: black\n }\n}'
)
is(css.toString(), '@page {\n a {\n color: black\n }\n}')
let page = css.first as AtRule
let a = page.first as Rule
let color = a.first as Declaration
expect(page.raws.before).toBeUndefined()
expect(color.raws.before).toBeUndefined()
expect(page.raws.between).toBeUndefined()
expect(color.raws.between).toBeUndefined()
expect(page.raws.after).toBeUndefined()
type(page.raws.before, 'undefined')
type(color.raws.before, 'undefined')
type(page.raws.between, 'undefined')
type(color.raws.between, 'undefined')
type(page.raws.after, 'undefined')
})
it('cleanRaws() keeps between on request', () => {
test('cleanRaws() keeps between on request', () => {
let css = parse('@page{a{color:black}}')
css.cleanRaws(true)
expect(css.toString()).toBe('@page{\n a{\n color:black\n }\n}')
is(css.toString(), '@page{\n a{\n color:black\n }\n}')
let page = css.first as AtRule
let a = page.first as Rule
let color = a.first as Declaration
expect(page.raws.between).toBeDefined()
expect(color.raws.between).toBeDefined()
expect(page.raws.before).toBeUndefined()
expect(color.raws.before).toBeUndefined()
expect(page.raws.after).toBeUndefined()
not.type(page.raws.between, 'undefined')
not.type(color.raws.between, 'undefined')
type(page.raws.before, 'undefined')
type(color.raws.before, 'undefined')
type(page.raws.after, 'undefined')
})
it('positionInside() returns position when node starts mid-line', () => {
test('positionInside() returns position when node starts mid-line', () => {
let css = parse('a { one: X }')
let a = css.first as Rule
let one = a.first as Declaration
expect(one.positionInside(6)).toEqual({ line: 1, column: 12 })
equal(one.positionInside(6), { line: 1, column: 12 })
})
it('positionInside() returns position when before contains newline', () => {
test('positionInside() returns position when before contains newline', () => {
let css = parse('a {\n one: X}')
let a = css.first as Rule
let one = a.first as Declaration
expect(one.positionInside(6)).toEqual({ line: 2, column: 9 })
equal(one.positionInside(6), { line: 2, column: 9 })
})
it('positionInside() returns position when node contains newlines', () => {
test('positionInside() returns position when node contains newlines', () => {
let css = parse('a {\n\tone: 1\n\t\tX\n3}')
let a = css.first as Rule
let one = a.first as Declaration
expect(one.positionInside(10)).toEqual({ line: 3, column: 4 })
equal(one.positionInside(10), { line: 3, column: 4 })
})
test.run()
Regular → Executable
+93 -89
View File
@@ -1,66 +1,68 @@
import { testPath, jsonify, eachTest } from 'postcss-parser-tests'
import { readFileSync } from 'fs'
import { resolve } from 'path'
import { test } from 'uvu'
import { equal, is, match, not, throws } from 'uvu/assert'
import { Declaration, AtRule, parse, Root, Rule } from '../lib/postcss.js'
it('works with file reads', () => {
test('works with file reads', () => {
let stream = readFileSync(testPath('atrule-empty.css'))
expect(parse(stream) instanceof Root).toBe(true)
is(parse(stream) instanceof Root, true)
})
eachTest((name, css, json) => {
it(`parses ${name}`, () => {
test(`parses ${name}`, () => {
css = css.replace(/\r\n/g, '\n')
let parsed = jsonify(parse(css, { from: name }))
expect(JSON.parse(parsed)).toEqual(JSON.parse(json))
equal(JSON.parse(parsed), JSON.parse(json))
})
})
it('parses UTF-8 BOM', () => {
test('parses UTF-8 BOM', () => {
let css = parse('\uFEFF@host { a {\f} }')
expect(css.nodes[0].raws.before).toBe('')
is(css.nodes[0].raws.before, '')
})
it('should has true at `hasBOM` property', () => {
test('should has true at `hasBOM` property', () => {
let css = parse('\uFEFF@host { a {\f} }')
expect(css.first?.source?.input.hasBOM).toBe(true)
is(css.first?.source?.input.hasBOM, true)
})
it('should has false at `hasBOM` property', () => {
test('should has false at `hasBOM` property', () => {
let css = parse('@host { a {\f} }')
expect(css.first?.source?.input.hasBOM).toBe(false)
is(css.first?.source?.input.hasBOM, false)
})
it('saves source file', () => {
test('saves source file', () => {
let css = parse('a {}', { from: 'a.css' })
expect(css.first?.source?.input.css).toBe('a {}')
expect(css.first?.source?.input.file).toEqual(resolve('a.css'))
expect(css.first?.source?.input.from).toEqual(resolve('a.css'))
is(css.first?.source?.input.css, 'a {}')
is(css.first?.source?.input.file, resolve('a.css'))
is(css.first?.source?.input.from, resolve('a.css'))
})
it('keeps absolute path in source', () => {
test('keeps absolute path in source', () => {
let css = parse('a {}', { from: 'http://example.com/a.css' })
expect(css.first?.source?.input.file).toBe('http://example.com/a.css')
expect(css.first?.source?.input.from).toBe('http://example.com/a.css')
is(css.first?.source?.input.file, 'http://example.com/a.css')
is(css.first?.source?.input.from, 'http://example.com/a.css')
})
it('saves source file on previous map', () => {
test('saves source file on previous map', () => {
let root1 = parse('a {}', { map: { inline: true } })
let css = root1.toResult({ map: { inline: true } }).css
let root2 = parse(css)
expect(root2.first?.source?.input.file).toEqual(resolve('to.css'))
is(root2.first?.source?.input.file, resolve('to.css'))
})
it('sets unique ID for file without name', () => {
test('sets unique ID for file without name', () => {
let css1 = parse('a {}')
let css2 = parse('a {}')
expect(css1.first?.source?.input.id).toMatch(/^<input css [\w-]+>$/)
expect(css1.first?.source?.input.from).toMatch(/^<input css [\w-]+>$/)
expect(css2.first?.source?.input.id).not.toEqual(css1.first?.source?.input.id)
match(String(css1.first?.source?.input.id), /^<input css [\w-]+>$/)
match(String(css1.first?.source?.input.from), /^<input css [\w-]+>$/)
is.not(css2.first?.source?.input.id, css1.first?.source?.input.id)
})
it('sets parent node', () => {
test('sets parent node', () => {
let file = testPath('atrule-rules.css')
let css = parse(readFileSync(file))
@@ -69,157 +71,159 @@ it('sets parent node', () => {
let from = keyframes.first as Rule
let decl = from.first as Declaration
expect(decl.parent).toBe(from)
expect(from.parent).toBe(keyframes)
expect(support.parent).toBe(css)
expect(keyframes.parent).toBe(support)
is(decl.parent, from)
is(from.parent, keyframes)
is(support.parent, css)
is(keyframes.parent, support)
})
it('ignores wrong close bracket', () => {
test('ignores wrong close bracket', () => {
let root = parse('a { p: ()) }')
let a = root.first as Rule
let decl = a.first as Declaration
expect(decl.value).toBe('())')
is(decl.value, '())')
})
it('parses unofficial --mixins', () => {
test('parses unofficial --mixins', () => {
let root = parse(':root { --x { color: pink; }; }')
let rule = root.first as Rule
let prop = rule.first as Rule
expect(prop.selector).toBe('--x')
is(prop.selector, '--x')
})
it('ignores symbols before declaration', () => {
test('ignores symbols before declaration', () => {
let root = parse('a { :one: 1 }')
let a = root.first as Rule
let prop = a.first as Declaration
expect(prop.raws.before).toBe(' :')
is(prop.raws.before, ' :')
})
it('parses double semicolon after rule', () => {
expect(parse('a { };;').toString()).toBe('a { };;')
test('parses double semicolon after rule', () => {
is(parse('a { };;').toString(), 'a { };;')
})
it('throws on unclosed blocks', () => {
expect(() => {
test('throws on unclosed blocks', () => {
throws(() => {
parse('\na {\n')
}).toThrow(/:2:1: Unclosed block/)
}, /:2:1: Unclosed block/)
})
it('throws on unnecessary block close', () => {
expect(() => {
test('throws on unnecessary block close', () => {
throws(() => {
parse('a {\n} }')
}).toThrow(/:2:3: Unexpected }/)
}, /:2:3: Unexpected }/)
})
it('throws on unclosed comment', () => {
expect(() => {
test('throws on unclosed comment', () => {
throws(() => {
parse('\n/*\n ')
}).toThrow(/:2:1: Unclosed comment/)
}, /:2:1: Unclosed comment/)
})
it('throws on unclosed quote', () => {
expect(() => {
test('throws on unclosed quote', () => {
throws(() => {
parse('\n"\n\na ')
}).toThrow(/:2:1: Unclosed string/)
}, /:2:1: Unclosed string/)
})
it('throws on unclosed bracket', () => {
expect(() => {
test('throws on unclosed bracket', () => {
throws(() => {
parse(':not(one() { }')
}).toThrow(/:1:5: Unclosed bracket/)
}, /:1:5: Unclosed bracket/)
})
it('throws on property without value', () => {
expect(() => {
test('throws on property without value', () => {
throws(() => {
parse('a { b;}')
}).toThrow(/:1:5: Unknown word/)
expect(() => {
}, /:1:5: Unknown word/)
throws(() => {
parse('a { b b }')
}).toThrow(/:1:5: Unknown word/)
}, /:1:5: Unknown word/)
})
it('throws on nameless at-rule', () => {
expect(() => {
test('throws on nameless at-rule', () => {
throws(() => {
parse('@')
}).toThrow(/:1:1: At-rule without name/)
}, /:1:1: At-rule without name/)
})
it('throws on property without semicolon', () => {
expect(() => {
test('throws on property without semicolon', () => {
throws(() => {
parse('a { one: filter(a:"") two: 2 }')
}).toThrow(/:1:21: Missed semicolon/)
}, /:1:21: Missed semicolon/)
})
it('throws on double colon', () => {
expect(() => {
test('throws on double colon', () => {
throws(() => {
parse('a { one:: 1 }')
}).toThrow(/:1:9: Double colon/)
}, /:1:9: Double colon/)
})
it('do not throws on comment in between', () => {
test('do not throws on comment in between', () => {
parse('a { b/* c */: 1 }')
})
it('throws on two words in between', () => {
expect(() => {
test('throws on two words in between', () => {
throws(() => {
parse('a { b c: 1 }')
}).toThrow(/:1:7: Unknown word/)
}, /:1:7: Unknown word/)
})
it('throws on just colon', () => {
expect(() => {
test('throws on just colon', () => {
throws(() => {
parse(':')
}).toThrow(/:1:1: Unknown word/)
expect(() => {
}, /:1:1: Unknown word/)
throws(() => {
parse(' : ')
}).toThrow(/:1:2: Unknown word/)
}, /:1:2: Unknown word/)
})
it('does not suggest different parsers for CSS', () => {
test('does not suggest different parsers for CSS', () => {
let error: any
try {
parse('a { one:: 1 }', { from: 'app.css' })
} catch (e) {
error = e
}
expect(error.message).not.toMatch(/postcss-less|postcss-scss/)
not.match(error.message, /postcss-less|postcss-scss/)
})
it('suggests postcss-scss for SCSS sources', () => {
expect(() => {
test('suggests postcss-scss for SCSS sources', () => {
throws(() => {
parse('a { #{var}: 1 }', { from: 'app.scss' })
}).toThrow(/postcss-scss/)
}, /postcss-scss/)
})
it('suggests postcss-sass for Sass sources', () => {
expect(() => {
test('suggests postcss-sass for Sass sources', () => {
throws(() => {
parse('a\n #{var}: 1', { from: 'app.sass' })
}).toThrow(/postcss-sass/)
}, /postcss-sass/)
})
it('suggests postcss-less for Less sources', () => {
expect(() => {
test('suggests postcss-less for Less sources', () => {
throws(() => {
parse('.@{my-selector} { }', { from: 'app.less' })
}).toThrow(/postcss-less/)
}, /postcss-less/)
})
it('should give the correct column of missed semicolon with !important', () => {
test('should give the correct column of missed semicolon with !important', () => {
let error: any
try {
parse('a { \n color: red !important\n background-color: black;\n}')
} catch (e) {
error = e
}
expect(error.message).toMatch(/2:26: Missed semicolon/)
match(error.message, /2:26: Missed semicolon/)
})
it('should give the correct column of missed semicolon without !important', () => {
test('should give the correct column of missed semicolon without !important', () => {
let error: any
try {
parse('a { \n color: red\n background-color: black;\n}')
} catch (e) {
error = e
}
expect(error.message).toMatch(/2:15: Missed semicolon/)
match(error.message, /2:15: Missed semicolon/)
})
test.run()
Regular → Executable
+105 -76
View File
@@ -1,91 +1,48 @@
import { test, suite } from 'uvu'
import { is, type, equal, match, throws } from 'uvu/assert'
import postcss, { Root, PluginCreator } from '../lib/postcss.js'
import Processor from '../lib/processor.js'
afterEach(() => {
jest.resetAllMocks()
})
const postcssSuite = suite('postcss suite')
it('creates plugins list', () => {
test('creates plugins list', () => {
let processor = postcss()
expect(processor instanceof Processor).toBe(true)
expect(processor.plugins).toEqual([])
is(processor instanceof Processor, true)
equal(processor.plugins, [])
})
it('saves plugins list', () => {
test('saves plugins list', () => {
let a = (): void => {}
let b = (): void => {}
expect(postcss(a, b).plugins).toEqual([a, b])
equal(postcss(a, b).plugins, [a, b])
})
it('saves plugins list as array', () => {
test('saves plugins list as array', () => {
let a = (): void => {}
let b = (): void => {}
expect(postcss([a, b]).plugins).toEqual([a, b])
equal(postcss([a, b]).plugins, [a, b])
})
it('takes plugin from other processor', () => {
test('takes plugin from other processor', () => {
let a = (): void => {}
let b = (): void => {}
let c = (): void => {}
let other = postcss([a, b])
expect(postcss([other, c]).plugins).toEqual([a, b, c])
equal(postcss([other, c]).plugins, [a, b, c])
})
it('takes plugins from a a plugin returning a processor', () => {
test('takes plugins from a a plugin returning a processor', () => {
let a = (): void => {}
let b = (): void => {}
let c = (): void => {}
let other = postcss([a, b])
let meta = (() => other) as PluginCreator<void>
meta.postcss = true
expect(postcss([other, c]).plugins).toEqual([a, b, c])
equal(postcss([other, c]).plugins, [a, b, c])
})
it('creates plugin', () => {
jest.spyOn(console, 'warn').mockImplementation(() => true)
let plugin = (postcss as any).plugin('test', (filter?: string) => {
return (root: Root) => {
root.walkDecls(filter ?? 'two', i => {
i.remove()
})
}
})
expect(console.warn).toHaveBeenCalledTimes(1)
expect(console.warn).toHaveBeenCalledWith(expect.stringMatching(/test/))
let func1: any = postcss(plugin).plugins[0]
expect(func1.postcssPlugin).toBe('test')
expect(func1.postcssVersion).toMatch(/\d+.\d+.\d+/)
let func2: any = postcss(plugin()).plugins[0]
expect(func2.postcssPlugin).toEqual(func1.postcssPlugin)
expect(func2.postcssVersion).toEqual(func1.postcssVersion)
let result1 = postcss(plugin('one')).process('a{ one: 1; two: 2 }')
expect(result1.css).toBe('a{ two: 2 }')
let result2 = postcss(plugin).process('a{ one: 1; two: 2 }')
expect(result2.css).toBe('a{ one: 1 }')
})
it('does not call plugin constructor', () => {
jest.spyOn(console, 'warn').mockImplementation(() => true)
let calls = 0
let plugin = (postcss as any).plugin('test', () => {
calls += 1
return () => {}
})
expect(calls).toBe(0)
expect(console.warn).toHaveBeenCalledTimes(1)
postcss(plugin).process('a{}')
expect(calls).toBe(1)
postcss(plugin()).process('a{}')
expect(calls).toBe(2)
})
it('creates a shortcut to process css', async () => {
test('creates a shortcut to process css', async () => {
let plugin = (postcss as any).plugin('test', (str?: string) => {
return (root: Root) => {
root.walkDecls(i => {
@@ -95,25 +52,25 @@ it('creates a shortcut to process css', async () => {
})
let result1 = plugin.process('a{value:foo}')
expect(result1.css).toBe('a{value:bar}')
is(result1.css, 'a{value:bar}')
let result2 = plugin.process('a{value:foo}', {}, 'baz')
expect(result2.css).toBe('a{value:baz}')
is(result2.css, 'a{value:baz}')
let result = await plugin.process('a{value:foo}', { from: 'a' }, 'baz')
expect(result.opts).toEqual({ from: 'a' })
expect(result.css).toBe('a{value:baz}')
equal(result.opts, { from: 'a' })
is(result.css, 'a{value:baz}')
})
it('contains parser', () => {
expect(postcss.parse('').type).toBe('root')
test('contains parser', () => {
is(postcss.parse('').type, 'root')
})
it('contains stringifier', () => {
expect(typeof postcss.stringify).toBe('function')
test('contains stringifier', () => {
type(postcss.stringify, 'function')
})
it('allows to build own CSS', () => {
test('allows to build own CSS', () => {
let root = postcss.root({ raws: { after: '\n' } })
let comment = postcss.comment({ text: 'Example' })
let media = postcss.atRule({ name: 'media', params: 'screen' })
@@ -125,7 +82,8 @@ it('allows to build own CSS', () => {
media.append(rule)
root.append(media)
expect(root.toString()).toEqual(
is(
root.toString(),
'/* Example */\n' +
'@media screen {\n' +
' a {\n' +
@@ -135,7 +93,7 @@ it('allows to build own CSS', () => {
)
})
it('allows to build own CSS with Document', () => {
test('allows to build own CSS with Document', () => {
let document = postcss.document()
let root = postcss.root({ raws: { after: '\n' } })
let comment = postcss.comment({ text: 'Example' })
@@ -149,7 +107,8 @@ it('allows to build own CSS with Document', () => {
root.append(media)
document.append(root)
expect(document.toString()).toEqual(
is(
document.toString(),
'/* Example */\n' +
'@media screen {\n' +
' a {\n' +
@@ -159,13 +118,83 @@ it('allows to build own CSS with Document', () => {
)
})
it('contains list module', () => {
expect(postcss.list.space('a b')).toEqual(['a', 'b'])
test('contains list module', () => {
equal(postcss.list.space('a b'), ['a', 'b'])
})
it('works with null', () => {
expect(() => {
test('works with null', () => {
throws(() => {
// @ts-expect-error
postcss([() => {}]).process(null).css
}).toThrow(/PostCSS received null instead of CSS string/)
}, /PostCSS received null instead of CSS string/)
})
test.run()
let _Warn: typeof global.console.warn
let _WarnArguments: any
let _WarnHaveBeenCalled: number
postcssSuite.before(() => {
_Warn = global.console.warn
_WarnHaveBeenCalled = 0
_WarnArguments = ''
global.console.warn = (...args) => {
_WarnArguments = args[0]
_WarnHaveBeenCalled++
}
})
postcssSuite.after.each(() => {
_WarnHaveBeenCalled = 0
_WarnArguments = ''
})
postcssSuite.after(() => {
global.console.warn = _Warn
})
postcssSuite.only('creates plugin', () => {
let plugin = (postcss as any).plugin('test', (filter?: string) => {
return (root: Root) => {
root.walkDecls(filter ?? 'two', i => {
i.remove()
})
}
})
is(_WarnHaveBeenCalled, 1)
match(_WarnArguments, /test/)
let func1: any = postcss(plugin).plugins[0]
is(func1.postcssPlugin, 'test')
match(func1.postcssVersion, /\d+.\d+.\d+/)
let func2: any = postcss(plugin()).plugins[0]
equal(func2.postcssPlugin, func1.postcssPlugin)
equal(func2.postcssVersion, func1.postcssVersion)
let result1 = postcss(plugin('one')).process('a{ one: 1; two: 2 }')
is(result1.css, 'a{ two: 2 }')
let result2 = postcss(plugin).process('a{ one: 1; two: 2 }')
is(result2.css, 'a{ one: 1 }')
})
postcssSuite.only('does not call plugin constructor', () => {
global.console.warn
let calls = 0
let plugin = (postcss as any).plugin('test', () => {
calls += 1
return () => {}
})
is(calls, 0)
is(_WarnHaveBeenCalled, 1)
postcss(plugin).process('a{}')
is(calls, 1)
postcss(plugin()).process('a{}')
is(calls, 2)
})
postcssSuite.run()
Regular → Executable
+65 -65
View File
@@ -3,6 +3,8 @@ import { SourceMapConsumer } from 'source-map-js'
import { pathToFileURL } from 'url'
import { existsSync } from 'fs'
import { join } from 'path'
import { test } from 'uvu'
import { is, type, equal, match, throws, not } from 'uvu/assert'
import { parse } from '../lib/postcss.js'
@@ -16,35 +18,35 @@ let mapObj = {
}
let map = JSON.stringify(mapObj)
afterEach(() => {
test.after.each(() => {
if (existsSync(dir)) removeSync(dir)
})
it('misses property if no map', () => {
expect(parse('a{}').source?.input.map).toBeUndefined()
test('misses property if no map', () => {
type(parse('a{}').source?.input.map, 'undefined')
})
it('creates property if map present', () => {
test('creates property if map present', () => {
let root = parse('a{}', { map: { prev: map } })
expect(root.source?.input.map.text).toEqual(map)
is(root.source?.input.map.text, map)
})
it('returns consumer', () => {
test('returns consumer', () => {
let obj = parse('a{}', { map: { prev: map } }).source?.input.map.consumer()
expect(obj instanceof SourceMapConsumer).toBe(true)
is(obj instanceof SourceMapConsumer, true)
})
it('sets annotation property', () => {
test('sets annotation property', () => {
let mapOpts = { map: { prev: map } }
let root1 = parse('a{}', mapOpts)
expect(root1.source?.input.map.annotation).toBeUndefined()
type(root1.source?.input.map.annotation, 'undefined')
let root2 = parse('a{}/*# sourceMappingURL=a.css.map */', mapOpts)
expect(root2.source?.input.map.annotation).toBe('a.css.map')
is(root2.source?.input.map.annotation, 'a.css.map')
})
it('checks previous sources content', () => {
test('checks previous sources content', () => {
let map2: any = {
version: 3,
file: 'b',
@@ -54,21 +56,21 @@ it('checks previous sources content', () => {
}
let opts = { map: { prev: map2 } }
expect(parse('a{}', opts).source?.input.map.withContent()).toBe(false)
is(parse('a{}', opts).source?.input.map.withContent(), false)
map2.sourcesContent = ['a{}']
expect(parse('a{}', opts).source?.input.map.withContent()).toBe(true)
is(parse('a{}', opts).source?.input.map.withContent(), true)
})
it('decodes base64 maps', () => {
test('decodes base64 maps', () => {
let b64 = Buffer.from(map).toString('base64')
let css =
'a{}\n' + `/*# sourceMappingURL=data:application/json;base64,${b64} */`
expect(parse(css).source?.input.map.text).toEqual(map)
is(parse(css).source?.input.map.text, map)
})
it('decodes base64 UTF-8 maps', () => {
test('decodes base64 UTF-8 maps', () => {
let b64 = Buffer.from(map).toString('base64')
let css =
'a{}\n/*# sourceMappingURL=data:application/json;' +
@@ -76,10 +78,10 @@ it('decodes base64 UTF-8 maps', () => {
b64 +
' */'
expect(parse(css).source?.input.map.text).toEqual(map)
is(parse(css).source?.input.map.text, map)
})
it('accepts different name for base64 maps with UTF-8 encoding', () => {
test('accepts different name for base64 maps with UTF-8 encoding', () => {
let b64 = Buffer.from(map).toString('base64')
let css =
'a{}\n/*# sourceMappingURL=data:application/json;' +
@@ -87,17 +89,17 @@ it('accepts different name for base64 maps with UTF-8 encoding', () => {
b64 +
' */'
expect(parse(css).source?.input.map.text).toEqual(map)
is(parse(css).source?.input.map.text, map)
})
it('decodes URI maps', () => {
test('decodes URI maps', () => {
let uri = 'data:application/json,' + decodeURI(map)
let css = `a{}\n/*# sourceMappingURL=${uri} */`
expect(parse(css).source?.input.map.text).toEqual(map)
is(parse(css).source?.input.map.text, map)
})
it('decodes URI UTF-8 maps', () => {
test('decodes URI UTF-8 maps', () => {
let uri = decodeURI(map)
let css =
'a{}\n/*# sourceMappingURL=data:application/json;' +
@@ -105,10 +107,10 @@ it('decodes URI UTF-8 maps', () => {
uri +
' */'
expect(parse(css).source?.input.map.text).toEqual(map)
is(parse(css).source?.input.map.text, map)
})
it('accepts different name for URI maps with UTF-8 encoding', () => {
test('accepts different name for URI maps with UTF-8 encoding', () => {
let uri = decodeURI(map)
let css =
'a{}\n/*# sourceMappingURL=data:application/json;' +
@@ -116,44 +118,44 @@ it('accepts different name for URI maps with UTF-8 encoding', () => {
uri +
' */'
expect(parse(css).source?.input.map.text).toEqual(map)
is(parse(css).source?.input.map.text, map)
})
it('removes map on request', () => {
test('removes map on request', () => {
let uri = 'data:application/json,' + decodeURI(map)
let css = `a{}\n/*# sourceMappingURL=${uri} */`
let input = parse(css, { map: { prev: false } }).source?.input
expect(input?.map).toBeUndefined()
type(input?.map, 'undefined')
})
it('raises on unknown inline encoding', () => {
test('raises on unknown inline encoding', () => {
let css =
'a { }\n/*# sourceMappingURL=data:application/json;' +
'md5,68b329da9893e34099c7d8ad5cb9c940*/'
expect(() => {
throws(() => {
parse(css)
}).toThrow('Unsupported source map encoding md5')
}, 'Unsupported source map encoding md5')
})
it('raises on unknown map format', () => {
expect(() => {
test('raises on unknown map format', () => {
throws(() => {
// @ts-expect-error
parse('a{}', { map: { prev: 1 } })
}).toThrow('Unsupported previous source map format: 1')
}, 'Unsupported previous source map format: 1')
})
it('reads map from annotation', () => {
test('reads map from annotation', () => {
let file = join(dir, 'a.map')
outputFileSync(file, map)
let root = parse('a{}\n/*# sourceMappingURL=a.map */', { from: file })
expect(root.source?.input.map.text).toEqual(map)
expect(root.source?.input.map.root).toEqual(dir)
is(root.source?.input.map.text, map)
is(root.source?.input.map.root, dir)
})
it('reads only the last map from annotation', () => {
test('reads only the last map from annotation', () => {
let file = join(dir, 'c.map')
outputFileSync(file, map)
let root = parse(
@@ -164,11 +166,11 @@ it('reads only the last map from annotation', () => {
{ from: file }
)
expect(root.source?.input.map.text).toEqual(map)
expect(root.source?.input.map.root).toEqual(dir)
is(root.source?.input.map.text, map)
is(root.source?.input.map.root, dir)
})
it('sets unique name for inline map', () => {
test('sets unique name for inline map', () => {
let map2 = {
version: 3,
sources: ['a'],
@@ -180,12 +182,12 @@ it('sets unique name for inline map', () => {
let file1 = parse('a{}', opts).source?.input.map.file
let file2 = parse('a{}', opts).source?.input.map.file
expect(file1).toMatch(/^<input css [\w-]+>$/)
expect(file1).not.toEqual(file2)
match(String(file1), /^<input css [\w-]+>$/)
is.not(file1, file2)
})
it('accepts an empty mappings string', () => {
expect(() => {
test('accepts an empty mappings string', () => {
not.throws(() => {
let emptyMap = {
version: 3,
sources: [],
@@ -193,10 +195,10 @@ it('accepts an empty mappings string', () => {
mappings: ''
}
parse('body{}', { map: { prev: emptyMap } })
}).not.toThrow()
})
})
it('accepts a function', () => {
test('accepts a function', () => {
let css = 'body{}\n/*# sourceMappingURL=a.map */'
let file = join(dir, 'previous-sourcemap-function.map')
outputFileSync(file, map)
@@ -206,13 +208,11 @@ it('accepts a function', () => {
}
}
let root = parse(css, opts)
expect(root.source?.input.map.text).toEqual(map)
expect(root.source?.input.map.annotation).toBe('a.map')
is(root.source?.input.map.text, map)
is(root.source?.input.map.annotation, 'a.map')
})
it('calls function with opts.from', () => {
expect.assertions(1)
test('calls function with opts.from', () => {
let css = 'body{}\n/*# sourceMappingURL=a.map */'
let file = join(dir, 'previous-sourcemap-function.map')
outputFileSync(file, map)
@@ -220,14 +220,14 @@ it('calls function with opts.from', () => {
from: 'a.css',
map: {
prev: from => {
expect(from).toBe('a.css')
is(from, 'a.css')
return file
}
}
})
})
it('raises when function returns invalid path', () => {
test('raises when function returns invalid path', () => {
let css = 'body{}\n/*# sourceMappingURL=a.map */'
let fakeMap = Number.MAX_SAFE_INTEGER.toString() + '.map'
let fakePath = join(dir, fakeMap)
@@ -236,12 +236,12 @@ it('raises when function returns invalid path', () => {
prev: () => fakePath
}
}
expect(() => {
throws(() => {
parse(css, opts)
}).toThrow('Unable to load previous source map: ' + fakePath)
}, 'Unable to load previous source map: ' + fakePath)
})
it('uses source map path as a root', () => {
test('uses source map path as a root', () => {
let from = join(dir, 'a.css')
outputFileSync(
join(dir, 'maps', 'a.map'),
@@ -254,7 +254,7 @@ it('uses source map path as a root', () => {
})
)
let root = parse('a{}\n/*# sourceMappingURL=maps/a.map */', { from })
expect(root.source?.input.origin(1, 1)).toEqual({
equal(root.source?.input.origin(1, 1), {
url: pathToFileURL(join(dir, '..', 'test.scss')).href,
file: join(dir, '..', 'test.scss'),
line: 1,
@@ -262,7 +262,7 @@ it('uses source map path as a root', () => {
})
})
it('uses current file path for source map', () => {
test('uses current file path for source map', () => {
let root = parse('a{b:1}', {
from: join(__dirname, 'dir', 'subdir', 'a.css'),
map: {
@@ -275,7 +275,7 @@ it('uses current file path for source map', () => {
}
}
})
expect(root.source?.input.origin(1, 1)).toEqual({
equal(root.source?.input.origin(1, 1), {
url: pathToFileURL(join(__dirname, 'dir', 'test.scss')).href,
file: join(__dirname, 'dir', 'test.scss'),
line: 1,
@@ -283,7 +283,7 @@ it('uses current file path for source map', () => {
})
})
it('works with non-file sources', () => {
test('works with non-file sources', () => {
let root = parse('a{b:1}', {
from: join(__dirname, 'dir', 'subdir', 'a.css'),
map: {
@@ -296,14 +296,14 @@ it('works with non-file sources', () => {
}
}
})
expect(root.source?.input.origin(1, 1)).toEqual({
equal(root.source?.input.origin(1, 1), {
url: 'http://example.com/test.scss',
line: 1,
column: 1
})
})
it('works with index map', () => {
test('works with index map', () => {
let root = parse('body {\nwidth:100%;\n}', {
from: join(__dirname, 'a.css'),
map: {
@@ -323,7 +323,7 @@ it('works with index map', () => {
}
}
})
expect((root as any).source.input.origin(1, 1).file).toEqual(
join(__dirname, 'b.css')
)
is((root as any).source.input.origin(1, 1).file, join(__dirname, 'b.css'))
})
test.run()
Regular → Executable
+178 -154
View File
@@ -1,5 +1,7 @@
import { resolve as pathResolve } from 'path'
import { delay } from 'nanodelay'
import { test } from 'uvu'
import { is, type, equal, match, throws, not } from 'uvu/assert'
import postcss, {
Plugin,
@@ -17,8 +19,42 @@ import LazyResult from '../lib/lazy-result.js'
import Processor from '../lib/processor.js'
import Rule from '../lib/rule.js'
afterEach(() => {
jest.resetAllMocks()
let _ConsoleWarn: typeof global.console.warn
let _ConsoleError: typeof global.console.error
let _ConsoleWarnArguments: any
let _ConsoleErrorArguments: any
let _ConsoleWarnHaveBeenCalled: number
let _ConsoleErrorHaveBeenCalled: number
test.before(() => {
_ConsoleWarn = global.console.warn
_ConsoleError = global.console.error
_ConsoleWarnArguments = ''
_ConsoleErrorArguments = ''
_ConsoleWarnHaveBeenCalled = 0
_ConsoleErrorHaveBeenCalled = 0
global.console.warn = (...args) => {
_ConsoleWarnArguments = args[0]
_ConsoleWarnHaveBeenCalled++
}
global.console.error = (...args) => {
_ConsoleErrorArguments = args[0]
_ConsoleErrorHaveBeenCalled++
}
})
test.before.each(() => {
_ConsoleWarnArguments = ''
_ConsoleErrorArguments = ''
_ConsoleWarnHaveBeenCalled = 0
_ConsoleErrorHaveBeenCalled = 0
})
test.after(() => {
global.console.warn = _ConsoleWarn
global.console.error = _ConsoleError
})
function prs(): Root {
@@ -29,10 +65,6 @@ function str(node: Node, builder: (s: string) => void): void {
builder(`${node.raws.after}!`)
}
function getCalls(func: any): any {
return func.mock.calls
}
async function catchError(cb: () => Promise<any>): Promise<Error> {
try {
await cb()
@@ -53,88 +85,88 @@ let beforeFix = new Processor([
}
])
it('adds new plugins', () => {
test('adds new plugins', () => {
let a = (): void => {}
let processor = new Processor()
processor.use(a)
expect(processor.plugins).toEqual([a])
equal(processor.plugins, [a])
})
it('adds new plugin by object', () => {
test('adds new plugin by object', () => {
let a = (): void => {}
let processor = new Processor()
processor.use({ postcss: a })
expect(processor.plugins).toEqual([a])
equal(processor.plugins, [a])
})
it('adds new plugin by object-function', () => {
test('adds new plugin by object-function', () => {
let a = (): void => {}
let obj: any = () => {}
obj.postcss = a
let processor = new Processor()
processor.use(obj)
expect(processor.plugins).toEqual([a])
equal(processor.plugins, [a])
})
it('adds new processors of another postcss instance', () => {
test('adds new processors of another postcss instance', () => {
let a = (): void => {}
let processor = new Processor()
let other = new Processor([a])
processor.use(other)
expect(processor.plugins).toEqual([a])
equal(processor.plugins, [a])
})
it('adds new processors from object', () => {
test('adds new processors from object', () => {
let a = (): void => {}
let processor = new Processor()
let other = new Processor([a])
processor.use({ postcss: other })
expect(processor.plugins).toEqual([a])
equal(processor.plugins, [a])
})
it('returns itself', () => {
test('returns itself', () => {
let a = (): void => {}
let b = (): void => {}
let processor = new Processor()
expect(processor.use(a).use(b).plugins).toEqual([a, b])
equal(processor.use(a).use(b).plugins, [a, b])
})
it('throws on wrong format', () => {
test('throws on wrong format', () => {
let pr = new Processor()
expect(() => {
throws(() => {
// @ts-expect-error
pr.use(1)
}).toThrow(/1 is not a PostCSS plugin/)
}, /1 is not a PostCSS plugin/)
})
it('processes CSS', () => {
test('processes CSS', () => {
let result = beforeFix.process('a::before{top:0}')
expect(result.css).toBe('a::before{content:"";top:0}')
is(result.css, 'a::before{content:"";top:0}')
})
it('processes parsed AST', () => {
test('processes parsed AST', () => {
let root = parse('a::before{top:0}')
let result = beforeFix.process(root)
expect(result.css).toBe('a::before{content:"";top:0}')
is(result.css, 'a::before{content:"";top:0}')
})
it('processes previous result', () => {
test('processes previous result', () => {
let result = new Processor([() => {}]).process('a::before{top:0}')
result = beforeFix.process(result)
expect(result.css).toBe('a::before{content:"";top:0}')
is(result.css, 'a::before{content:"";top:0}')
})
it('takes maps from previous result', () => {
test('takes maps from previous result', () => {
let one = new Processor([() => {}]).process('a{}', {
from: 'a.css',
to: 'b.css',
map: { inline: false }
})
let two = new Processor([() => {}]).process(one, { to: 'c.css' })
expect(two.map.toJSON().sources).toEqual(['a.css'])
equal(two.map.toJSON().sources, ['a.css'])
})
it('inlines maps from previous result', () => {
test('inlines maps from previous result', () => {
let one = new Processor([() => {}]).process('a{}', {
from: 'a.css',
to: 'b.css',
@@ -144,10 +176,10 @@ it('inlines maps from previous result', () => {
to: 'c.css',
map: { inline: true }
})
expect(two.map).toBeUndefined()
type(two.map, 'undefined')
})
it('throws with file name', () => {
test('throws with file name', () => {
let error: CssSyntaxError | undefined
try {
new Processor([() => {}]).process('a {', { from: 'a.css' }).css
@@ -159,29 +191,27 @@ it('throws with file name', () => {
}
}
expect(error?.file).toEqual(pathResolve('a.css'))
expect(error?.message).toMatch(/a.css:1:1: Unclosed block$/)
is(error?.file, pathResolve('a.css'))
match(String(error?.message), /a.css:1:1: Unclosed block$/)
})
it('allows to replace Root', () => {
test('allows to replace Root', () => {
let processor = new Processor([
(css, result) => {
result.root = new Root()
}
])
expect(processor.process('a {}').css).toBe('')
is(processor.process('a {}').css, '')
})
it('returns LazyResult object', () => {
test('returns LazyResult object', () => {
let result = new Processor([() => {}]).process('a{}')
expect(result instanceof LazyResult).toBe(true)
expect(result.css).toBe('a{}')
expect(result.toString()).toBe('a{}')
is(result instanceof LazyResult, true)
is(result.css, 'a{}')
is(result.toString(), 'a{}')
})
it('calls all plugins once', async () => {
expect.assertions(1)
test('calls all plugins once', async () => {
let calls = ''
let a = (): void => {
calls += 'a'
@@ -195,33 +225,33 @@ it('calls all plugins once', async () => {
result.map
result.root
await result
expect(calls).toBe('ab')
is(calls, 'ab')
})
it('parses, converts and stringifies CSS', () => {
expect(
test('parses, converts and stringifies CSS', () => {
is(
typeof new Processor([
(css: Root) => {
expect(css instanceof Root).toBe(true)
equal(css instanceof Root, true)
}
]).process('a {}').css
).toBe('string')
]).process('a {}').css,
'string'
)
})
it('send result to plugins', () => {
expect.assertions(4)
test('send result to plugins', () => {
let processor = new Processor([() => {}])
return processor
processor
.use((css, result) => {
expect(result instanceof Result).toBe(true)
expect(result.processor).toEqual(processor)
expect(result.opts).toEqual({ map: true })
expect(result.root).toEqual(css)
is(result instanceof Result, true)
equal(result.processor, processor)
equal(result.opts, { map: true })
equal(result.root, css)
})
.process('a {}', { map: true, from: undefined })
})
it('accepts source map from PostCSS', () => {
test('accepts source map from PostCSS', () => {
let one = new Processor([() => {}]).process('a{}', {
from: 'a.css',
to: 'b.css',
@@ -232,17 +262,17 @@ it('accepts source map from PostCSS', () => {
to: 'c.css',
map: { prev: one.map, inline: false }
})
expect(two.map.toJSON().sources).toEqual(['a.css'])
equal(two.map.toJSON().sources, ['a.css'])
})
it('supports async plugins', async () => {
test('supports async plugins', async () => {
let starts = 0
let finish = 0
let async1 = (css: Root): Promise<void> =>
new Promise<void>(resolve => {
starts += 1
setTimeout(() => {
expect(starts).toBe(1)
equal(starts, 1)
css.append('a {}')
finish += 1
@@ -251,8 +281,8 @@ it('supports async plugins', async () => {
})
let async2 = (css: Root): Promise<void> =>
new Promise<void>(resolve => {
expect(starts).toBe(1)
expect(finish).toBe(1)
equal(starts, 1)
equal(finish, 1)
starts += 1
setTimeout(() => {
@@ -262,19 +292,17 @@ it('supports async plugins', async () => {
}, 1)
})
let r = await new Processor([async1, async2]).process('', { from: 'a' })
expect(starts).toBe(2)
expect(finish).toBe(2)
expect(r.css).toBe('a {}b {}')
is(starts, 2)
is(finish, 2)
is(r.css, 'a {}b {}')
})
it('works async without plugins', async () => {
test('works async without plugins', async () => {
let r = await new Processor([() => {}]).process('a {}', { from: 'a' })
expect(r.css).toBe('a {}')
is(r.css, 'a {}')
})
it('runs async plugin only once', async () => {
expect.assertions(1)
test('runs async plugin only once', async () => {
let calls = 0
let async = (): Promise<void> => {
return new Promise<void>(resolve => {
@@ -289,10 +317,10 @@ it('runs async plugin only once', async () => {
result.then(() => {})
await result
await result
expect(calls).toBe(1)
is(calls, 1)
})
it('supports async errors', async () => {
test('supports async errors', async () => {
let error = new Error('Async')
let async = (): Promise<void> => {
return new Promise<void>((resolve, reject) => {
@@ -301,17 +329,17 @@ it('supports async errors', async () => {
}
let result = new Processor([async]).process('', { from: undefined })
let err1 = await catchError(async () => await result)
expect(err1).toEqual(error)
equal(err1, error)
let err2: Error | undefined
result.catch(catched => {
err2 = catched
})
await delay(10)
expect(err2).toEqual(error)
equal(err2, error)
})
it('supports sync errors in async mode', async () => {
test('supports sync errors in async mode', async () => {
let error = new Error('Async')
let async = (): void => {
throw error
@@ -319,40 +347,40 @@ it('supports sync errors in async mode', async () => {
let err = await catchError(() =>
new Processor([async]).process('', { from: undefined })
)
expect(err).toEqual(error)
equal(err, error)
})
it('throws parse error in async', async () => {
test('throws parse error in async', async () => {
let err = await catchError(() =>
new Processor([() => {}]).process('a{', { from: undefined })
)
expect(err.message).toBe('<css input>:1:1: Unclosed block')
is(err.message, '<css input>:1:1: Unclosed block')
})
it('throws error on sync method to async plugin', () => {
test('throws error on sync method to async plugin', () => {
let async = (): Promise<void> => {
return new Promise<void>(resolve => {
resolve()
})
}
expect(() => {
throws(() => {
new Processor([async]).process('a{}').css
}).toThrow(/async/)
}, /async/)
})
it('throws a sync call in async running', async () => {
test('throws a sync call in async running', async () => {
let async = (): Promise<void> =>
new Promise<void>(resolve => setTimeout(resolve, 1))
let processor = new Processor([async]).process('a{}', { from: 'a.css' })
processor.async()
expect(() => {
throws(() => {
processor.sync()
}).toThrow(/then/)
}, /then/)
})
it('remembers errors', async () => {
test('remembers errors', async () => {
let calls = 0
let plugin: Plugin = {
postcssPlugin: 'plugin',
@@ -364,15 +392,15 @@ it('remembers errors', async () => {
let processing = postcss([plugin]).process('a{}', { from: undefined })
expect(() => {
throws(() => {
processing.css
}).toThrow('test')
expect(() => {
}, 'test')
throws(() => {
processing.css
}).toThrow('test')
expect(() => {
}, 'test')
throws(() => {
processing.root
}).toThrow('test')
}, 'test')
let asyncError: any
try {
@@ -380,19 +408,18 @@ it('remembers errors', async () => {
} catch (e) {
asyncError = e
}
expect(asyncError.message).toBe('test')
is(asyncError.message, 'test')
expect(calls).toBe(1)
is(calls, 1)
})
it('checks plugin compatibility', () => {
jest.spyOn(console, 'warn').mockImplementation(() => true)
test('checks plugin compatibility', () => {
let plugin = (postcss as any).plugin('test', () => {
return () => {
throw new Error('Er')
}
})
expect(console.warn).toHaveBeenCalledTimes(1)
is(_ConsoleWarnHaveBeenCalled, 1)
let func = plugin()
func.postcssVersion = '2.1.5'
@@ -402,151 +429,145 @@ it('checks plugin compatibility', () => {
processor.process('a{}').css
}
jest.spyOn(console, 'error').mockImplementation(() => {})
expect(() => {
throws(() => {
processBy('1.0.0')
}).toThrow('Er')
expect(getCalls(console.error)).toHaveLength(1)
expect(getCalls(console.error)[0][0]).toEqual(
}, 'Er')
is(_ConsoleErrorHaveBeenCalled, 1)
is(
_ConsoleErrorArguments,
'Unknown error from PostCSS plugin. ' +
'Your current PostCSS version is 1.0.0, but test uses 2.1.5. ' +
'Perhaps this is the source of the error below.'
)
expect(() => {
throws(() => {
processBy('3.0.0')
}).toThrow('Er')
expect(getCalls(console.error)).toHaveLength(2)
}, 'Er')
is(_ConsoleErrorHaveBeenCalled, 2)
expect(() => {
throws(() => {
processBy('2.0.0')
}).toThrow('Er')
expect(getCalls(console.error)).toHaveLength(3)
}, 'Er')
is(_ConsoleErrorHaveBeenCalled, 3)
expect(() => {
throws(() => {
processBy('2.1.0')
}).toThrow('Er')
expect(getCalls(console.error)).toHaveLength(3)
}, 'Er')
is(_ConsoleErrorHaveBeenCalled, 3)
})
it('sets last plugin to result', async () => {
test('sets last plugin to result', async () => {
let plugin1 = (css: Root, result: Result): void => {
expect(result.lastPlugin).toBe(plugin1)
equal(result.lastPlugin, plugin1)
}
let plugin2 = (css: Root, result: Result): void => {
expect(result.lastPlugin).toBe(plugin2)
equal(result.lastPlugin, plugin2)
}
let processor = new Processor([plugin1, plugin2])
let result = await processor.process('a{}', { from: undefined })
expect(result.lastPlugin).toBe(plugin2)
equal(result.lastPlugin, plugin2)
})
it('uses custom parsers', async () => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
test('uses custom parsers', async () => {
let processor = new Processor([])
let result = await processor.process('a{}', { parser: prs, from: undefined })
expect(console.warn).not.toHaveBeenCalled()
expect(result.css).toBe('ok')
not.ok(_ConsoleWarnHaveBeenCalled)
is(result.css, 'ok')
})
it('uses custom parsers from object', async () => {
test('uses custom parsers from object', async () => {
let processor = new Processor([])
let syntax = { parse: prs, stringify: str }
let result = await processor.process('a{}', { parser: syntax, from: 'a' })
expect(result.css).toBe('ok')
equal(result.css, 'ok')
})
it('uses custom stringifier', async () => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
test('uses custom stringifier', async () => {
let processor = new Processor([])
let result = await processor.process('a{}', { stringifier: str, from: 'a' })
expect(console.warn).not.toHaveBeenCalled()
expect(result.css).toBe('!')
not.ok(_ConsoleWarnHaveBeenCalled)
is(result.css, '!')
})
it('uses custom stringifier from object', async () => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
test('uses custom stringifier from object', async () => {
let processor = new Processor([])
let syntax = { parse: prs, stringify: str }
let result = await processor.process('', { stringifier: syntax, from: 'a' })
expect(console.warn).not.toHaveBeenCalled()
expect(result.css).toBe('!')
not.ok(_ConsoleWarnHaveBeenCalled)
is(result.css, '!')
})
it('uses custom stringifier with source maps', async () => {
test('uses custom stringifier with source maps', async () => {
let processor = new Processor([])
let result = await processor.process('a{}', {
map: true,
stringifier: str,
from: undefined
})
expect(result.css).toMatch(/!\n\/\*# sourceMap/)
match(result.css, /!\n\/\*# sourceMap/)
})
it('uses custom syntax', async () => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
test('uses custom syntax', async () => {
let processor = new Processor([() => {}])
let result = await processor.process('a{}', {
syntax: { parse: prs, stringify: str },
from: undefined
})
expect(console.warn).not.toHaveBeenCalled()
expect(result.css).toBe('ok!')
not.ok(_ConsoleWarnHaveBeenCalled)
is(result.css, 'ok!')
})
it('contains PostCSS version', () => {
expect(new Processor().version).toMatch(/\d+.\d+.\d+/)
test('contains PostCSS version', () => {
match(new Processor().version, /\d+.\d+.\d+/)
})
it('throws on syntax as plugin', () => {
test('throws on syntax as plugin', () => {
let processor = new Processor([() => {}])
expect(() => {
throws(() => {
processor.use({
// @ts-expect-error
parse() {}
})
}).toThrow(/syntax/)
}, /syntax/)
})
it('warns about missed from', async () => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
test('warns about missed from', async () => {
let processor = new Processor([() => {}])
processor.process('a{}').css
expect(console.warn).not.toHaveBeenCalled()
not.ok(_ConsoleWarnHaveBeenCalled)
await processor.process('a{}')
expect(console.warn).toHaveBeenCalledWith(
is(
_ConsoleWarnArguments,
'Without `from` option PostCSS could generate wrong source map ' +
'and will not find Browserslist config. Set it to CSS file path ' +
'or to `undefined` to prevent this warning.'
)
})
it('warns about missed plugins', async () => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
test('warns about missed plugins', async () => {
await new Processor().process('a{}')
expect(console.warn).toHaveBeenCalledWith(
is(
_ConsoleWarnArguments,
'You did not set any plugins, parser, or stringifier. ' +
'Right now, PostCSS does nothing. Pick plugins for your case ' +
'on https://www.postcss.parts/ and use them in postcss.config.js.'
)
})
it('supports plugins returning processors', () => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
test('supports plugins returning processors', () => {
let a = (): void => {}
let processor = new Processor()
let other: any = (postcss as any).plugin('test', () => {
return new Processor([a])
})
processor.use(other)
expect(processor.plugins).toEqual([a])
equal(processor.plugins, [a])
})
it('supports plugin creators returning processors', () => {
test('supports plugin creators returning processors', () => {
let a = (): void => {}
let processor = new Processor()
let other = (() => {
@@ -554,10 +575,10 @@ it('supports plugin creators returning processors', () => {
}) as PluginCreator<void>
other.postcss = true
processor.use(other)
expect(processor.plugins).toEqual([a])
equal(processor.plugins, [a])
})
it('uses custom syntax for document', async () => {
test('uses custom syntax for document', async () => {
let customParser: Parser<Document> = () => {
return new Document({
nodes: [
@@ -605,7 +626,8 @@ it('uses custom syntax for document', async () => {
from: undefined
})
expect(result.css).toEqual(
is(
result.css,
'<html>\n<head>\n<style id="id1">' +
'a {}\n\n\n' +
'</style>\n<style id="id2">' +
@@ -613,3 +635,5 @@ it('uses custom syntax for document', async () => {
'</style>\n</head>'
)
})
test.run()
Regular → Executable
+17 -11
View File
@@ -1,16 +1,19 @@
import { test } from 'uvu'
import { is, equal } from 'uvu/assert'
import postcss, { Warning, Result, Root, Plugin } from '../lib/postcss.js'
import Processor from '../lib/processor.js'
let processor = new Processor()
let root = new Root()
it('stringifies', () => {
test('stringifies', () => {
let result = new Result(processor, root, {})
result.css = 'a{}'
expect(`${result}`).toEqual(result.css)
is(`${result}`, result.css)
})
it('adds warning', () => {
test('adds warning', () => {
let warning
let plugin: Plugin = {
postcssPlugin: 'test-plugin',
@@ -20,17 +23,18 @@ it('adds warning', () => {
}
let result = postcss([plugin]).process('a{}').sync()
expect(warning).toEqual(
equal(
warning,
new Warning('test', {
plugin: 'test-plugin',
node: result.root.first
})
)
expect(result.messages).toEqual([warning])
equal(result.messages, [warning])
})
it('allows to override plugin', () => {
test('allows to override plugin', () => {
let plugin: Plugin = {
postcssPlugin: 'test-plugin',
Once(css, { result }) {
@@ -39,26 +43,28 @@ it('allows to override plugin', () => {
}
let result = postcss([plugin]).process('a{}').sync()
expect(result.messages[0].plugin).toBe('test-plugin#one')
is(result.messages[0].plugin, 'test-plugin#one')
})
it('allows Root', () => {
test('allows Root', () => {
let css = postcss.parse('a{}')
let result = new Result(processor, css, {})
result.warn('TT', { node: css.first })
expect(result.messages[0].toString()).toBe('<css input>:1:1: TT')
is(result.messages[0].toString(), '<css input>:1:1: TT')
})
it('returns only warnings', () => {
test('returns only warnings', () => {
let result = new Result(processor, root, {})
result.messages = [
{ type: 'warning', text: 'a' },
{ type: 'custom' },
{ type: 'warning', text: 'b' }
]
expect(result.warnings()).toEqual([
equal(result.warnings(), [
{ type: 'warning', text: 'a' },
{ type: 'warning', text: 'b' }
])
})
test.run()
Regular → Executable
+25 -20
View File
@@ -1,62 +1,67 @@
import { test } from 'uvu'
import { is, type, match } from 'uvu/assert'
import { Result, parse } from '../lib/postcss.js'
it('prepend() fixes spaces on insert before first', () => {
test('prepend() fixes spaces on insert before first', () => {
let css = parse('a {} b {}')
css.prepend({ selector: 'em' })
expect(css.toString()).toBe('em {} a {} b {}')
is(css.toString(), 'em {} a {} b {}')
})
it('prepend() fixes spaces on multiple inserts before first', () => {
test('prepend() fixes spaces on multiple inserts before first', () => {
let css = parse('a {} b {}')
css.prepend({ selector: 'em' }, { selector: 'strong' })
expect(css.toString()).toBe('em {} strong {} a {} b {}')
is(css.toString(), 'em {} strong {} a {} b {}')
})
it('prepend() uses default spaces on only first', () => {
test('prepend() uses default spaces on only first', () => {
let css = parse('a {}')
css.prepend({ selector: 'em' })
expect(css.toString()).toBe('em {}\na {}')
is(css.toString(), 'em {}\na {}')
})
it('append() sets new line between rules in multiline files', () => {
test('append() sets new line between rules in multiline files', () => {
let a = parse('a {}\n\na {}\n')
let b = parse('b {}\n')
expect(a.append(b).toString()).toBe('a {}\n\na {}\n\nb {}\n')
is(a.append(b).toString(), 'a {}\n\na {}\n\nb {}\n')
})
it('insertAfter() does not use before of first rule', () => {
test('insertAfter() does not use before of first rule', () => {
let css = parse('a{} b{}')
css.insertAfter(0, { selector: '.a' })
css.insertAfter(2, { selector: '.b' })
expect(css.nodes[1].raws.before).toBeUndefined()
expect(css.nodes[3].raws.before).toBe(' ')
expect(css.toString()).toBe('a{} .a{} b{} .b{}')
type(css.nodes[1].raws.before, 'undefined')
is(css.nodes[3].raws.before, ' ')
is(css.toString(), 'a{} .a{} b{} .b{}')
})
it('fixes spaces on removing first rule', () => {
test('fixes spaces on removing first rule', () => {
let css = parse('a{}\nb{}\n')
if (!css.first) throw new Error('No nodes were parsed')
css.first.remove()
expect(css.toString()).toBe('b{}\n')
is(css.toString(), 'b{}\n')
})
it('keeps spaces on moving root', () => {
test('keeps spaces on moving root', () => {
let css1 = parse('a{}\nb{}\n')
let css2 = parse('')
css2.append(css1)
expect(css2.toString()).toBe('a{}\nb{}')
is(css2.toString(), 'a{}\nb{}')
let css3 = parse('\n')
css3.append(css2.nodes)
expect(css3.toString()).toBe('a{}\nb{}\n')
is(css3.toString(), 'a{}\nb{}\n')
})
it('generates result with map', () => {
test('generates result with map', () => {
let root = parse('a {}')
let result = root.toResult({ map: true })
expect(result instanceof Result).toBe(true)
expect(result.css).toMatch(/a {}\n\/\*# sourceMappingURL=/)
is(result instanceof Result, true)
match(result.css, /a {}\n\/\*# sourceMappingURL=/)
})
test.run()
Regular → Executable
+31 -29
View File
@@ -1,79 +1,81 @@
import { test } from 'uvu'
import { is, equal } from 'uvu/assert'
import { Rule, parse } from '../lib/postcss.js'
it('initializes with properties', () => {
test('initializes with properties', () => {
let rule = new Rule({ selector: 'a' })
expect(rule.selector).toBe('a')
is(rule.selector, 'a')
})
it('returns array in selectors', () => {
test('returns array in selectors', () => {
let rule = new Rule({ selector: 'a,b' })
expect(rule.selectors).toEqual(['a', 'b'])
equal(rule.selectors, ['a', 'b'])
})
it('trims selectors', () => {
test('trims selectors', () => {
let rule = new Rule({ selector: '.a\n, .b , .c' })
expect(rule.selectors).toEqual(['.a', '.b', '.c'])
equal(rule.selectors, ['.a', '.b', '.c'])
})
it('is smart about selectors commas', () => {
test('is smart about selectors commas', () => {
let rule = new Rule({
selector: "[foo='a, b'], a:-moz-any(:focus, [href*=','])"
})
expect(rule.selectors).toEqual([
"[foo='a, b']",
"a:-moz-any(:focus, [href*=','])"
])
equal(rule.selectors, ["[foo='a, b']", "a:-moz-any(:focus, [href*=','])"])
})
it('receive array in selectors', () => {
test('receive array in selectors', () => {
let rule = new Rule({ selector: 'i, b' })
rule.selectors = ['em', 'strong']
expect(rule.selector).toBe('em, strong')
is(rule.selector, 'em, strong')
})
it('saves separator in selectors', () => {
test('saves separator in selectors', () => {
let rule = new Rule({ selector: 'i,\nb' })
rule.selectors = ['em', 'strong']
expect(rule.selector).toBe('em,\nstrong')
is(rule.selector, 'em,\nstrong')
})
it('uses between to detect separator in selectors', () => {
test('uses between to detect separator in selectors', () => {
let rule = new Rule({ selector: 'b', raws: { between: '' } })
rule.selectors = ['b', 'strong']
expect(rule.selector).toBe('b,strong')
is(rule.selector, 'b,strong')
})
it('uses space in separator be default in selectors', () => {
test('uses space in separator be default in selectors', () => {
let rule = new Rule({ selector: 'b' })
rule.selectors = ['b', 'strong']
expect(rule.selector).toBe('b, strong')
is(rule.selector, 'b, strong')
})
it('selectors works in constructor', () => {
test('selectors works in constructor', () => {
let rule = new Rule({ selectors: ['a', 'b'] })
expect(rule.selector).toBe('a, b')
is(rule.selector, 'a, b')
})
it('inserts default spaces', () => {
test('inserts default spaces', () => {
let rule = new Rule({ selector: 'a' })
expect(rule.toString()).toBe('a {}')
is(rule.toString(), 'a {}')
rule.append({ prop: 'color', value: 'black' })
expect(rule.toString()).toBe('a {\n color: black\n}')
is(rule.toString(), 'a {\n color: black\n}')
})
it('clones spaces from another rule', () => {
test('clones spaces from another rule', () => {
let root = parse('b{\n }')
let rule = new Rule({ selector: 'em' })
root.append(rule)
expect(root.toString()).toBe('b{\n }\nem{\n }')
is(root.toString(), 'b{\n }\nem{\n }')
})
it('uses different spaces for empty rules', () => {
test('uses different spaces for empty rules', () => {
let root = parse('a{}\nb{\n a:1\n}')
let rule = new Rule({ selector: 'em' })
root.append(rule)
expect(root.toString()).toBe('a{}\nb{\n a:1\n}\nem{}')
is(root.toString(), 'a{}\nb{\n a:1\n}\nem{}')
rule.append({ prop: 'top', value: '0' })
expect(root.toString()).toBe('a{}\nb{\n a:1\n}\nem{\n top:0\n}')
is(root.toString(), 'a{}\nb{\n a:1\n}\nem{\n top:0\n}')
})
test.run()
Regular → Executable
+83 -68
View File
@@ -1,109 +1,122 @@
let { Declaration, AtRule, Node, Root, Rule, Document, parse } = require('..')
let { test } = require('uvu')
let { is } = require('uvu/assert')
let {
Declaration,
AtRule,
Node,
Root,
Rule,
Document,
parse
} = require('../lib/postcss')
let Stringifier = require('../lib/stringifier')
let str
beforeAll(() => {
test.before.each(() => {
str = new Stringifier()
})
it('creates trimmed/raw property', () => {
test('creates trimmed/raw property', () => {
let b = new Node({ one: 'trim' })
b.raws.one = { value: 'trim', raw: 'raw' }
expect(str.rawValue(b, 'one')).toBe('raw')
is(str.rawValue(b, 'one'), 'raw')
b.one = 'trim1'
expect(str.rawValue(b, 'one')).toBe('trim1')
is(str.rawValue(b, 'one'), 'trim1')
})
it('works without rawValue magic', () => {
test('works without rawValue magic', () => {
let b = new Node()
b.one = '1'
expect(b.one).toBe('1')
expect(str.rawValue(b, 'one')).toBe('1')
is(b.one, '1')
is(str.rawValue(b, 'one'), '1')
})
it('uses node raw', () => {
test('uses node raw', () => {
let rule = new Rule({ selector: 'a', raws: { between: '\n' } })
expect(str.raw(rule, 'between', 'beforeOpen')).toBe('\n')
is(str.raw(rule, 'between', 'beforeOpen'), '\n')
})
it('hacks before for nodes without parent', () => {
test('hacks before for nodes without parent', () => {
let rule = new Rule({ selector: 'a' })
expect(str.raw(rule, 'before')).toBe('')
is(str.raw(rule, 'before'), '')
})
it('hacks before for first node', () => {
test('hacks before for first node', () => {
let root = new Root()
root.append(new Rule({ selector: 'a' }))
expect(str.raw(root.first, 'before')).toBe('')
is(str.raw(root.first, 'before'), '')
})
it('hacks before for first decl', () => {
test('hacks before for first decl', () => {
let decl = new Declaration({ prop: 'color', value: 'black' })
expect(str.raw(decl, 'before')).toBe('')
is(str.raw(decl, 'before'), '')
let rule = new Rule({ selector: 'a' })
rule.append(decl)
expect(str.raw(decl, 'before')).toBe('\n ')
is(str.raw(decl, 'before'), '\n ')
})
it('detects after raw', () => {
test('detects after raw', () => {
let root = new Root()
root.append({ selector: 'a', raws: { after: ' ' } })
root.first.append({ prop: 'color', value: 'black' })
root.append({ selector: 'a' })
expect(str.raw(root.last, 'after')).toBe(' ')
is(str.raw(root.last, 'after'), ' ')
})
it('uses defaults without parent', () => {
test('uses defaults without parent', () => {
let rule = new Rule({ selector: 'a' })
expect(str.raw(rule, 'between', 'beforeOpen')).toBe(' ')
is(str.raw(rule, 'between', 'beforeOpen'), ' ')
})
it('uses defaults for unique node', () => {
test('uses defaults for unique node', () => {
let root = new Root()
root.append(new Rule({ selector: 'a' }))
expect(str.raw(root.first, 'between', 'beforeOpen')).toBe(' ')
is(str.raw(root.first, 'between', 'beforeOpen'), ' ')
})
it('clones raw from first node', () => {
test('clones raw from first node', () => {
let root = new Root()
root.append(new Rule({ selector: 'a', raws: { between: '' } }))
root.append(new Rule({ selector: 'b' }))
expect(str.raw(root.last, 'between', 'beforeOpen')).toBe('')
is(str.raw(root.last, 'between', 'beforeOpen'), '')
})
it('indents by default', () => {
test('indents by default', () => {
let root = new Root()
root.append(new AtRule({ name: 'page' }))
root.first.append(new Rule({ selector: 'a' }))
root.first.first.append({ prop: 'color', value: 'black' })
expect(root.toString()).toEqual(
is(
root.toString(),
'@page {\n' + ' a {\n' + ' color: black\n' + ' }\n' + '}'
)
})
it('clones style', () => {
test('clones style', () => {
let compress = parse('@page{ a{ } }')
let spaces = parse('@page {\n a {\n }\n}')
compress.first.first.append({ prop: 'color', value: 'black' })
expect(compress.toString()).toBe('@page{ a{ color: black } }')
is(compress.toString(), '@page{ a{ color: black } }')
spaces.first.first.append({ prop: 'color', value: 'black' })
expect(spaces.toString()).toBe('@page {\n a {\n color: black\n }\n}')
is(spaces.toString(), '@page {\n a {\n color: black\n }\n}')
})
it('clones indent', () => {
test('clones indent', () => {
let root = parse('a{\n}')
root.first.append({ text: 'a' })
root.first.append({ text: 'b', raws: { before: '\n\n ' } })
expect(root.toString()).toBe('a{\n\n /* a */\n\n /* b */\n}')
is(root.toString(), 'a{\n\n /* a */\n\n /* b */\n}')
})
it('clones declaration before for comment', () => {
test('clones declaration before for comment', () => {
let root = parse('a{\n}')
root.first.append({ text: 'a' })
root.first.append({
@@ -111,94 +124,94 @@ it('clones declaration before for comment', () => {
value: '1',
raws: { before: '\n\n ' }
})
expect(root.toString()).toBe('a{\n\n /* a */\n\n a: 1\n}')
is(root.toString(), 'a{\n\n /* a */\n\n a: 1\n}')
})
it('clones indent by types', () => {
test('clones indent by types', () => {
let css = parse('a {\n *color: black\n}\n\nb {\n}')
css.append(new Rule({ selector: 'em' }))
css.last.append({ prop: 'z-index', value: '1' })
expect(css.last.first.raw('before')).toBe('\n ')
is(css.last.first.raw('before'), '\n ')
})
it('ignores non-space symbols in indent cloning', () => {
test('ignores non-space symbols in indent cloning', () => {
let css = parse('a {\n color: black\n}\n\nb {\n}')
css.append(new Rule({ selector: 'em' }))
css.last.append({ prop: 'z-index', value: '1' })
expect(css.last.raw('before')).toBe('\n\n')
expect(css.last.first.raw('before')).toBe('\n ')
is(css.last.raw('before'), '\n\n')
is(css.last.first.raw('before'), '\n ')
})
it('clones indent by before and after', () => {
test('clones indent by before and after', () => {
let css = parse('@page{\n\n a{\n color: black}}')
css.first.append(new Rule({ selector: 'b' }))
css.first.last.append({ prop: 'z-index', value: '1' })
expect(css.first.last.raw('before')).toBe('\n\n ')
expect(css.first.last.raw('after')).toBe('')
is(css.first.last.raw('before'), '\n\n ')
is(css.first.last.raw('after'), '')
})
it('clones semicolon only from rules with children', () => {
test('clones semicolon only from rules with children', () => {
let css = parse('a{}b{one:1;}')
expect(str.raw(css.first, 'semicolon')).toBe(true)
is(str.raw(css.first, 'semicolon'), true)
})
it('clones only spaces in before', () => {
test('clones only spaces in before', () => {
let css = parse('a{*one:1}')
css.first.append({ prop: 'two', value: '2' })
css.append({ name: 'keyframes', params: 'a' })
css.last.append({ selector: 'from' })
expect(css.toString()).toBe('a{*one:1;two:2}\n@keyframes a{\nfrom{}}')
is(css.toString(), 'a{*one:1;two:2}\n@keyframes a{\nfrom{}}')
})
it('clones only spaces in between', () => {
test('clones only spaces in between', () => {
let css = parse('a{one/**/:1}')
css.first.append({ prop: 'two', value: '2' })
expect(css.toString()).toBe('a{one/**/:1;two:2}')
is(css.toString(), 'a{one/**/:1;two:2}')
})
it('uses optional raws.indent', () => {
test('uses optional raws.indent', () => {
let rule = new Rule({ selector: 'a', raws: { indent: ' ' } })
rule.append({ prop: 'color', value: 'black' })
expect(rule.toString()).toBe('a {\n color: black\n}')
is(rule.toString(), 'a {\n color: black\n}')
})
it('handles nested roots', () => {
test('handles nested roots', () => {
let root = new Root()
let subRoot = new Root()
subRoot.append(new AtRule({ name: 'foo' }))
root.append(subRoot)
expect(root.toString()).toBe('@foo')
is(root.toString(), '@foo')
})
it('handles root', () => {
test('handles root', () => {
let root = new Root()
root.append(new AtRule({ name: 'foo' }))
let s = root.toString()
expect(s).toBe('@foo')
is(s, '@foo')
})
it('handles root with after', () => {
test('handles root with after', () => {
let root = new Root({ raws: { after: ' ' } })
root.append(new AtRule({ name: 'foo' }))
let s = root.toString()
expect(s).toBe('@foo ')
is(s, '@foo ')
})
it('pass nodes to document', () => {
test('pass nodes to document', () => {
let root = new Root()
let document = new Document({ nodes: [root] })
expect(document.toString()).toBe('')
is(document.toString(), '')
})
it('handles document with one root', () => {
test('handles document with one root', () => {
let root = new Root()
root.append(new AtRule({ name: 'foo' }))
@@ -207,10 +220,10 @@ it('handles document with one root', () => {
let s = document.toString()
expect(s).toBe('@foo')
is(s, '@foo')
})
it('handles document with one root and after raw', () => {
test('handles document with one root and after raw', () => {
let document = new Document()
let root = new Root({ raws: { after: ' ' } })
root.append(new AtRule({ name: 'foo' }))
@@ -218,10 +231,10 @@ it('handles document with one root and after raw', () => {
let s = document.toString()
expect(s).toBe('@foo ')
is(s, '@foo ')
})
it('handles document with one root and before and after', () => {
test('handles document with one root and before and after', () => {
let document = new Document()
let root = new Root({ raws: { after: 'AFTER' } })
root.append(new AtRule({ name: 'foo' }))
@@ -229,10 +242,10 @@ it('handles document with one root and before and after', () => {
let s = document.toString()
expect(s).toBe('@fooAFTER')
is(s, '@fooAFTER')
})
it('handles document with three roots without raws', () => {
test('handles document with three roots without raws', () => {
let root1 = new Root()
root1.append(new AtRule({ name: 'foo' }))
@@ -249,10 +262,10 @@ it('handles document with three roots without raws', () => {
let s = document.toString()
expect(s).toBe('@fooa {}color: black')
is(s, '@fooa {}color: black')
})
it('handles document with three roots, with before and after raws', () => {
test('handles document with three roots, with before and after raws', () => {
let root1 = new Root({ raws: { after: 'AFTER_ONE' } })
root1.append(new Rule({ selector: 'a.one' }))
@@ -269,5 +282,7 @@ it('handles document with three roots, with before and after raws', () => {
let s = document.toString()
expect(s).toBe('a.one {}AFTER_ONEa.two {}AFTER_TWOa.three {}AFTER_THREE')
is(s, 'a.one {}AFTER_ONEa.two {}AFTER_TWOa.three {}AFTER_THREE')
})
test.run()
Regular → Executable
+6 -2
View File
@@ -1,16 +1,20 @@
import { eachTest } from 'postcss-parser-tests'
import { test } from 'uvu'
import { is } from 'uvu/assert'
import { stringify, parse } from '../lib/postcss.js'
eachTest((name, css) => {
if (name === 'bom.css') return
it(`stringifies ${name}`, () => {
test(`stringifies ${name}`, () => {
let root = parse(css)
let result = ''
stringify(root, i => {
result += i
})
expect(result).toEqual(css)
is(result, css)
})
})
test.run()
Regular → Executable
+52 -47
View File
@@ -1,5 +1,8 @@
let { test } = require('uvu')
let { equal, throws, is } = require('uvu/assert')
let tokenizer = require('../lib/tokenize')
let { Input } = require('..')
let { Input } = require('../lib/postcss')
function tokenize(css, opts) {
let processor = tokenizer(new Input(css), opts)
@@ -11,29 +14,29 @@ function tokenize(css, opts) {
}
function run(css, tokens, opts) {
expect(tokenize(css, opts)).toEqual(tokens)
equal(tokenize(css, opts), tokens)
}
it('tokenizes empty file', () => {
test('tokenizes empty file', () => {
run('', [])
})
it('tokenizes space', () => {
test('tokenizes space', () => {
run('\r\n \f\t', [['space', '\r\n \f\t']])
})
it('tokenizes word', () => {
test('tokenizes word', () => {
run('ab', [['word', 'ab', 0, 1]])
})
it('splits word by !', () => {
test('splits word by !', () => {
run('aa!bb', [
['word', 'aa', 0, 1],
['word', '!bb', 2, 4]
])
})
it('changes lines in spaces', () => {
test('changes lines in spaces', () => {
run('a \n b', [
['word', 'a', 0, 0],
['space', ' \n '],
@@ -41,7 +44,7 @@ it('changes lines in spaces', () => {
])
})
it('tokenizes control chars', () => {
test('tokenizes control chars', () => {
run('{:;}', [
['{', '{', 0],
[':', ':', 1],
@@ -50,7 +53,7 @@ it('tokenizes control chars', () => {
])
})
it('escapes control symbols', () => {
test('escapes control symbols', () => {
run('\\(\\{\\"\\@\\\\""', [
['word', '\\(', 0, 1],
['word', '\\{', 2, 3],
@@ -61,18 +64,18 @@ it('escapes control symbols', () => {
])
})
it('escapes backslash', () => {
test('escapes backslash', () => {
run('\\\\\\\\{', [
['word', '\\\\\\\\', 0, 3],
['{', '{', 4]
])
})
it('tokenizes simple brackets', () => {
test('tokenizes simple brackets', () => {
run('(ab)', [['brackets', '(ab)', 0, 3]])
})
it('tokenizes square brackets', () => {
test('tokenizes square brackets', () => {
run('a[bc]', [
['word', 'a', 0, 0],
['[', '[', 1],
@@ -81,7 +84,7 @@ it('tokenizes square brackets', () => {
])
})
it('tokenizes complicated brackets', () => {
test('tokenizes complicated brackets', () => {
run('(())("")(/**/)(\\\\)(\n)(', [
['(', '(', 0],
['brackets', '()', 1, 2],
@@ -102,32 +105,32 @@ it('tokenizes complicated brackets', () => {
])
})
it('tokenizes string', () => {
test('tokenizes string', () => {
run('\'"\'"\\""', [
['string', "'\"'", 0, 2],
['string', '"\\""', 3, 6]
])
})
it('tokenizes escaped string', () => {
test('tokenizes escaped string', () => {
run('"\\\\"', [['string', '"\\\\"', 0, 3]])
})
it('changes lines in strings', () => {
test('changes lines in strings', () => {
run('"\n\n""\n\n"', [
['string', '"\n\n"', 0, 3],
['string', '"\n\n"', 4, 7]
])
})
it('tokenizes at-word', () => {
test('tokenizes at-word', () => {
run('@word ', [
['at-word', '@word', 0, 4],
['space', ' ']
])
})
it('tokenizes at-word end', () => {
test('tokenizes at-word end', () => {
run('@one{@two()@three""@four;', [
['at-word', '@one', 0, 3],
['{', '{', 4],
@@ -140,14 +143,14 @@ it('tokenizes at-word end', () => {
])
})
it('tokenizes urls', () => {
test('tokenizes urls', () => {
run('url(/*\\))', [
['word', 'url', 0, 2],
['brackets', '(/*\\))', 3, 8]
])
})
it('tokenizes quoted urls', () => {
test('tokenizes quoted urls', () => {
run('url(")")', [
['word', 'url', 0, 2],
['(', '(', 3],
@@ -156,15 +159,15 @@ it('tokenizes quoted urls', () => {
])
})
it('tokenizes at-symbol', () => {
test('tokenizes at-symbol', () => {
run('@', [['at-word', '@', 0, 0]])
})
it('tokenizes comment', () => {
test('tokenizes comment', () => {
run('/* a\nb */', [['comment', '/* a\nb */', 0, 8]])
})
it('changes lines in comments', () => {
test('changes lines in comments', () => {
run('a/* \n */b', [
['word', 'a', 0, 0],
['comment', '/* \n */', 1, 7],
@@ -172,7 +175,7 @@ it('changes lines in comments', () => {
])
})
it('supports line feed', () => {
test('supports line feed', () => {
run('a\fb', [
['word', 'a', 0, 0],
['space', '\f'],
@@ -180,7 +183,7 @@ it('supports line feed', () => {
])
})
it('supports carriage return', () => {
test('supports carriage return', () => {
run('a\rb\r\nc', [
['word', 'a', 0, 0],
['space', '\r'],
@@ -190,7 +193,7 @@ it('supports carriage return', () => {
])
})
it('tokenizes CSS', () => {
test('tokenizes CSS', () => {
let css =
'a {\n' +
' content: "a";\n' +
@@ -228,25 +231,25 @@ it('tokenizes CSS', () => {
])
})
it('throws error on unclosed string', () => {
expect(() => {
test('throws error on unclosed string', () => {
throws(() => {
tokenize(' "')
}).toThrow(/:1:2: Unclosed string/)
}, /:1:2: Unclosed string/)
})
it('throws error on unclosed comment', () => {
expect(() => {
test('throws error on unclosed comment', () => {
throws(() => {
tokenize(' /*')
}).toThrow(/:1:2: Unclosed comment/)
}, /:1:2: Unclosed comment/)
})
it('throws error on unclosed url', () => {
expect(() => {
test('throws error on unclosed url', () => {
throws(() => {
tokenize('url(')
}).toThrow(/:1:4: Unclosed bracket/)
}, /:1:4: Unclosed bracket/)
})
it('ignores unclosing string on request', () => {
test('ignores unclosing string on request', () => {
run(
' "',
[
@@ -257,7 +260,7 @@ it('ignores unclosing string on request', () => {
)
})
it('ignores unclosing comment on request', () => {
test('ignores unclosing comment on request', () => {
run(
' /*',
[
@@ -268,7 +271,7 @@ it('ignores unclosing comment on request', () => {
)
})
it('ignores unclosing function on request', () => {
test('ignores unclosing function on request', () => {
run(
'url(',
[
@@ -279,7 +282,7 @@ it('ignores unclosing function on request', () => {
)
})
it('tokenizes hexadecimal escape', () => {
test('tokenizes hexadecimal escape', () => {
run('\\0a \\09 \\z ', [
['word', '\\0a ', 0, 3],
['word', '\\09 ', 4, 7],
@@ -288,7 +291,7 @@ it('tokenizes hexadecimal escape', () => {
])
})
it('ignore unclosed per token request', () => {
test('ignore unclosed per token request', () => {
function token(css, opts) {
let processor = tokenizer(new Input(css), opts)
let tokens = []
@@ -311,19 +314,21 @@ it('ignore unclosed per token request', () => {
['(', '(', 15]
]
expect(tokens).toEqual(expected)
equal(tokens, expected)
})
it('provides correct position', () => {
test('provides correct position', () => {
let css = 'Three tokens'
let processor = tokenizer(new Input(css))
expect(processor.position()).toBe(0)
is(processor.position(), 0)
processor.nextToken()
expect(processor.position()).toBe(5)
is(processor.position(), 5)
processor.nextToken()
expect(processor.position()).toBe(6)
is(processor.position(), 6)
processor.nextToken()
expect(processor.position()).toBe(12)
is(processor.position(), 12)
processor.nextToken()
expect(processor.position()).toBe(12)
is(processor.position(), 12)
})
test.run()
Regular → Executable
+171 -152
View File
@@ -1,5 +1,7 @@
import { resolve, basename } from 'path'
import { delay } from 'nanodelay'
import { test } from 'uvu'
import { is, type, equal, throws } from 'uvu/assert'
import postcss, {
Container,
@@ -250,40 +252,39 @@ let postcssAlias = createPlugin(() => {
}
})
it('works classic plugin replace-color', async () => {
test('works classic plugin replace-color', async () => {
let { css } = await postcss([replaceColorGreenClassic]).process(
'.a{ color: red; } ' + '.b{ will-change: transform; }',
{
from: 'a.css'
}
)
expect(css).toEqual('.a{ color: green; } ' + '.b{ will-change: transform; }')
is(css, '.a{ color: green; } ' + '.b{ will-change: transform; }')
})
it('works visitor plugin will-change', async () => {
test('works visitor plugin will-change', async () => {
let { css } = postcss([willChangeVisitor]).process(
'.foo { will-change: transform; }',
{ from: 'a.css' }
)
expect(css).toBe(
'.foo { backface-visibility: hidden; will-change: transform; }'
)
is(css, '.foo { backface-visibility: hidden; will-change: transform; }')
})
it('works visitor plugin add-prop', async () => {
test('works visitor plugin add-prop', async () => {
let { css } = await postcss([addPropsVisitor]).process(
'.a{ color: red; } .b{ will-change: transform; }',
{
from: 'a.css'
}
)
expect(css).toEqual(
is(
css,
'.a{ will-change: transform; color: red; } ' +
'.b{ will-change: transform; }'
)
})
it('works visitor plugin add-prop in document with single root', async () => {
test('works visitor plugin add-prop in document with single root', async () => {
let document = postcss.document({
nodes: [postcss.parse('.a{ color: red; } .b{ will-change: transform; }')]
})
@@ -291,13 +292,14 @@ it('works visitor plugin add-prop in document with single root', async () => {
let { css } = await postcss([addPropsVisitor]).process(document, {
from: 'a.css'
})
expect(css).toEqual(
is(
css,
'.a{ will-change: transform; color: red; } ' +
'.b{ will-change: transform; }'
)
})
it('works visitor plugin add-prop in document with two roots', async () => {
test('works visitor plugin add-prop in document with two roots', async () => {
let document = postcss.document({
nodes: [
postcss.parse('.a{ color: red; }'),
@@ -308,18 +310,18 @@ it('works visitor plugin add-prop in document with two roots', async () => {
let { css } = await postcss([addPropsVisitor]).process(document, {
from: 'a.css'
})
expect(css).toEqual('.a{ color: red; }' + '.b{ will-change: transform; }')
is(css, '.a{ color: red; }' + '.b{ will-change: transform; }')
})
it('works with at-rule params', () => {
test('works with at-rule params', () => {
let { css } = postcss([replacePrintToMobile, replaceScreenToPrint]).process(
'@media (screen) {}',
{ from: 'a.css' }
)
expect(css).toBe('@media (mobile) {}')
is(css, '@media (mobile) {}')
})
it('wraps node to proxies', () => {
test('wraps node to proxies', () => {
let proxy: any
let root: Root | undefined
postcss({
@@ -333,16 +335,22 @@ it('wraps node to proxies', () => {
}).process('a{color:black}', { from: 'a.css' }).css
if (!root) throw new Error('Nodes were not catched')
let rule = root.first as Rule
expect(proxy.proxyOf).toBe(rule)
expect(proxy.root().proxyOf).toBe(rule.root())
expect(proxy.nodes[0].proxyOf).toBe(rule.first)
expect(proxy.first.proxyOf).toBe(rule.first)
expect(proxy.unknown).toBeUndefined()
expect(proxy.some((decl: Declaration) => decl.prop === 'color')).toBe(true)
expect(proxy.every((decl: Declaration) => decl.prop === 'color')).toBe(true)
equal(proxy.proxyOf, rule)
equal(proxy.root().proxyOf, rule.root())
equal(proxy.nodes[0].proxyOf, rule.first)
equal(proxy.first.proxyOf, rule.first)
type(proxy.unknown, 'undefined')
is(
proxy.some((decl: Declaration) => decl.prop === 'color'),
true
)
is(
proxy.every((decl: Declaration) => decl.prop === 'color'),
true
)
let props: string[] = []
proxy.walkDecls((decl: Declaration) => props.push(decl.prop))
expect(props).toEqual(['color'])
equal(props, ['color'])
})
const cssThree = '.a{ color: red; } .b{ will-change: transform; }'
@@ -355,22 +363,22 @@ const expectedThree =
'} ' +
'.b{ backface-visibility: hidden; will-change: transform; }'
it('work of three plug-ins; sequence 1', async () => {
test('work of three plug-ins; sequence 1', async () => {
let { css } = await postcss([
replaceColorGreenClassic,
willChangeVisitor,
addPropsVisitor
]).process(cssThree, { from: 'a.css' })
expect(css).toEqual(expectedThree)
is(css, expectedThree)
})
it('work of three plug-ins; sequence 2', async () => {
test('work of three plug-ins; sequence 2', async () => {
let { css } = await postcss([
addPropsVisitor,
replaceColorGreenClassic,
willChangeVisitor
]).process(cssThree, { from: 'a.css' })
expect(css).toEqual(expectedThree)
is(css, expectedThree)
})
const cssThreeDocument = postcss.document({
@@ -384,53 +392,53 @@ const expectedThreeDocument =
'.a{ color: green; }' +
'.b{ backface-visibility: hidden; will-change: transform; }'
it('work of three plug-ins in a document; sequence 1', async () => {
test('work of three plug-ins in a document; sequence 1', async () => {
let { css } = await postcss([
replaceColorGreenClassic,
willChangeVisitor,
addPropsVisitor
]).process(cssThreeDocument, { from: 'a.css' })
expect(css).toEqual(expectedThreeDocument)
is(css, expectedThreeDocument)
})
it('work of three plug-ins in a document; sequence 2', async () => {
test('work of three plug-ins in a document; sequence 2', async () => {
let { css } = await postcss([
addPropsVisitor,
replaceColorGreenClassic,
willChangeVisitor
]).process(cssThreeDocument, { from: 'a.css' })
expect(css).toEqual(expectedThreeDocument)
is(css, expectedThreeDocument)
})
const cssThroughProps = '.a{color: yellow;}'
const expectedThroughProps = '.a{color: red;}'
it('change in node values through props; sequence 1', async () => {
test('change in node values through props; sequence 1', async () => {
let { css } = await postcss([
replaceGreenToRed,
replaceAllButRedToGreen
]).process(cssThroughProps, { from: 'a.css' })
expect(css).toEqual(expectedThroughProps)
is(css, expectedThroughProps)
})
it('change in node values through props; sequence 2', async () => {
test('change in node values through props; sequence 2', async () => {
let { css } = await postcss([
replaceAllButRedToGreen,
replaceGreenToRed
]).process(cssThroughProps, { from: 'a.css' })
expect(css).toEqual(expectedThroughProps)
is(css, expectedThroughProps)
})
it('works visitor plugin postcss-focus', async () => {
test('works visitor plugin postcss-focus', async () => {
let input = '*:focus { outline: 0; }.button:hover { background: red; }'
let expected =
'*:focus { outline: 0; }' +
'.button:hover, .button:focus { background: red; }'
let { css } = await postcss([postcssFocus]).process(input, { from: 'a.css' })
expect(css).toEqual(expected)
is(css, expected)
})
it('works visitor plugin hidden', async () => {
test('works visitor plugin hidden', async () => {
let input = 'h2{' + 'display: hidden;' + '}'
let expected =
@@ -452,7 +460,7 @@ it('works visitor plugin hidden', async () => {
'}'
let { css } = await postcss([hidden]).process(input, { from: 'a.css' })
expect(css).toEqual(expected)
is(css, expected)
})
let cssFocusHidden =
@@ -484,30 +492,30 @@ let expectedFocusHidden =
'clear: both;' +
'}'
it('works visitor plugins postcss-focus and hidden; sequence 1', async () => {
test('works visitor plugins postcss-focus and hidden; sequence 1', async () => {
let { css } = await postcss([hidden, postcssFocus]).process(cssFocusHidden, {
from: 'a.css'
})
expect(css).toEqual(expectedFocusHidden)
is(css, expectedFocusHidden)
})
it('works visitor plugins postcss-focus and hidden; sequence 2', async () => {
test('works visitor plugins postcss-focus and hidden; sequence 2', async () => {
let { css } = await postcss([postcssFocus, hidden]).process(cssFocusHidden, {
from: 'a.css'
})
expect(css).toEqual(expectedFocusHidden)
is(css, expectedFocusHidden)
})
it('works visitor plugin postcss-alias', async () => {
test('works visitor plugin postcss-alias', async () => {
let input =
'@alias { fs: font-size; bg: background; }' +
'.aliased { fs: 16px; bg: white; }'
let expected = '.aliased { font-size: 16px; background: white; }'
let { css } = postcss([postcssAlias]).process(input, { from: 'a.css' })
expect(css).toEqual(expected)
is(css, expected)
})
it('adds plugin to error', async () => {
test('adds plugin to error', async () => {
let broken: Plugin = {
postcssPlugin: 'broken',
Rule(rule) {
@@ -520,12 +528,12 @@ it('adds plugin to error', async () => {
} catch (e) {
error = e
}
expect(error.message).toEqual(`broken: ${resolve('broken.css')}:1:1: test`)
expect(error.postcssNode.toString()).toBe('a{}')
expect(error.stack).toContain('broken.css:1:1')
is(error.message, `broken: ${resolve('broken.css')}:1:1: test`)
is(error.postcssNode.toString(), 'a{}')
is(error.stack.includes('broken.css:1:1'), true)
})
it('adds plugin to async error', async () => {
test('adds plugin to async error', async () => {
let broken: Plugin = {
postcssPlugin: 'broken',
async Rule(rule) {
@@ -539,12 +547,12 @@ it('adds plugin to async error', async () => {
} catch (e) {
error = e
}
expect(error.message).toEqual(`broken: ${resolve('broken.css')}:1:1: test`)
expect(error.postcssNode.toString()).toBe('a{}')
expect(error.stack).toContain('broken.css:1:1')
is(error.message, `broken: ${resolve('broken.css')}:1:1: test`)
is(error.postcssNode.toString(), 'a{}')
is(error.stack.includes('broken.css:1:1'), true)
})
it('adds sync plugin to async error', async () => {
test('adds sync plugin to async error', async () => {
let broken: Plugin = {
postcssPlugin: 'broken',
Rule(rule) {
@@ -557,12 +565,12 @@ it('adds sync plugin to async error', async () => {
} catch (e) {
error = e
}
expect(error.message).toEqual(`broken: ${resolve('broken.css')}:1:1: test`)
expect(error.postcssNode.toString()).toBe('a{}')
expect(error.stack).toContain('broken.css:1:1')
is(error.message, `broken: ${resolve('broken.css')}:1:1: test`)
is(error.postcssNode.toString(), 'a{}')
is(error.stack.includes('broken.css:1:1'), true)
})
it('adds node to error', async () => {
test('adds node to error', async () => {
let broken: Plugin = {
postcssPlugin: 'broken',
Rule() {
@@ -575,12 +583,12 @@ it('adds node to error', async () => {
} catch (e) {
error = e
}
expect(error.message).toBe('test')
expect(error.postcssNode.toString()).toBe('a{}')
expect(error.stack).toContain('broken.css:1:1')
is(error.message, 'test')
is(error.postcssNode.toString(), 'a{}')
is(error.stack.includes('broken.css:1:1'), true)
})
it('adds node to async error', async () => {
test('adds node to async error', async () => {
let broken: Plugin = {
postcssPlugin: 'broken',
async Rule() {
@@ -594,12 +602,12 @@ it('adds node to async error', async () => {
} catch (e) {
error = e
}
expect(error.message).toBe('test')
expect(error.postcssNode.toString()).toBe('a{}')
expect(error.stack).toContain('broken.css:1:1')
is(error.message, 'test')
is(error.postcssNode.toString(), 'a{}')
is(error.stack.includes('broken.css:1:1'), true)
})
it('shows error on sync call async plugins', () => {
test('shows error on sync call async plugins', () => {
let asyncPlugin: Plugin = {
postcssPlugin: 'asyncPlugin',
async Rule() {}
@@ -610,16 +618,16 @@ it('shows error on sync call async plugins', () => {
} catch (e) {
error = e
}
expect(error.message).toContain('work with async plugins')
is(error.message.includes('work with async plugins'), true)
})
it('passes helpers', async () => {
test('passes helpers', async () => {
function check(node: AnyNode, helpers: Helpers): void {
expect(helpers.result.messages).toEqual([])
expect(typeof helpers.postcss).toBe('function')
expect(helpers.comment().type).toBe('comment')
expect(new helpers.Comment().type).toBe('comment')
expect(helpers.list).toBe(postcss.list)
equal(helpers.result.messages, [])
is(typeof helpers.postcss, 'function')
is(helpers.comment().type, 'comment')
is(new helpers.Comment().type, 'comment')
equal(helpers.list, postcss.list)
}
let syncPlugin: Plugin = {
@@ -650,13 +658,13 @@ it('passes helpers', async () => {
await postcss([asyncPlugin]).process('a{}', { from: 'a.css' })
})
it('passes helpers in a document', async () => {
test('passes helpers in a document', async () => {
function check(node: AnyNode, helpers: Helpers): void {
expect(helpers.result.messages).toEqual([])
expect(typeof helpers.postcss).toBe('function')
expect(helpers.comment().type).toBe('comment')
expect(new helpers.Comment().type).toBe('comment')
expect(helpers.list).toBe(postcss.list)
equal(helpers.result.messages, [])
type(helpers.postcss, 'function')
is(helpers.comment().type, 'comment')
is(new helpers.Comment().type, 'comment')
equal(helpers.list, postcss.list)
}
let syncPlugin: Plugin = {
@@ -693,21 +701,22 @@ it('passes helpers in a document', async () => {
)
})
it('detects non-changed values', () => {
test('detects non-changed values', () => {
let plugin: Plugin = {
postcssPlugin: 'test',
Declaration(decl) {
decl.value = 'red'
}
}
expect(
is(
postcss([plugin]).process('a{ color: black; background: white; }', {
from: 'a.css'
}).css
).toBe('a{ color: red; background: red; }')
}).css,
'a{ color: red; background: red; }'
)
})
it('allows runtime listeners', () => {
test('allows runtime listeners', () => {
let root = false
let plugin: Plugin = {
postcssPlugin: 'test',
@@ -725,13 +734,14 @@ it('allows runtime listeners', () => {
decl.value = 'red'
}
}
expect(
postcss([plugin]).process('a{ color: black }', { from: 'a.css' }).css
).toBe('a.css{ color: red }')
expect(root).toBe(true)
is(
postcss([plugin]).process('a{ color: black }', { from: 'a.css' }).css,
'a.css{ color: red }'
)
is(root, true)
})
it('works correctly with nodes changes', () => {
test('works correctly with nodes changes', () => {
let plugin: Plugin = {
postcssPlugin: 'test',
Rule(rule) {
@@ -740,22 +750,23 @@ it('works correctly with nodes changes', () => {
}
}
}
expect(
postcss([plugin]).process('a{ color: black }', { from: 'a.css' }).css
).toBe('a{ z-index: 1; color: black }')
is(
postcss([plugin]).process('a{ color: black }', { from: 'a.css' }).css,
'a{ z-index: 1; color: black }'
)
})
it('throws error on unknown plugin property', () => {
test('throws error on unknown plugin property', () => {
let plugin: any = {
postcssPlugin: 'test',
NO: true
}
expect(() => {
throws(() => {
postcss([plugin]).process('').css
}).toThrow(/Unknown event NO in test\. Try to update PostCSS \(\d/)
}, /Unknown event NO in test\. Try to update PostCSS \(\d/)
})
it('unwraps nodes on inserting', () => {
test('unwraps nodes on inserting', () => {
let moveNode: Plugin = {
postcssPlugin: 'moveNode',
Declaration: {
@@ -768,7 +779,7 @@ it('unwraps nodes on inserting', () => {
}
let root = postcss([moveNode]).process('a{color:red}').root
expect((root.last as any).proxyOf).toBe(root.last)
equal((root.last as any).proxyOf, root.last)
})
let redToGreen: Plugin = {
@@ -828,8 +839,8 @@ let insertFirst: Plugin = {
}
}
for (let type of ['sync', 'async']) {
it(`walks ${type} through tree`, async () => {
for (let funcType of ['sync', 'async']) {
test(`walks ${funcType} through tree`, async () => {
let [visits, visitor] = buildVisitor()
let processor = postcss([visitor]).process(
`@media screen {
@@ -844,12 +855,13 @@ for (let type of ['sync', 'async']) {
}`,
{ from: 'a.css' }
)
if (type === 'sync') {
if (funcType === 'sync') {
processor.css
} else {
await processor
}
expect(addIndex(visits)).toEqual(
equal(
addIndex(visits),
addIndex([
['Once', '1'],
['Root', '1'],
@@ -873,7 +885,7 @@ for (let type of ['sync', 'async']) {
)
})
it(`walks ${type} through tree in a document`, async () => {
test(`walks ${funcType} through tree in a document`, async () => {
let document = postcss.document({
nodes: [
postcss.parse(`@media screen {
@@ -891,13 +903,14 @@ for (let type of ['sync', 'async']) {
let [visits, visitor] = buildVisitor()
let processor = postcss([visitor]).process(document, { from: 'a.css' })
if (type === 'sync') {
if (funcType === 'sync') {
processor.css
} else {
await processor
}
expect(addIndex(visits)).toEqual(
equal(
addIndex(visits),
addIndex([
['Once', '1'],
['Document', '1'],
@@ -923,7 +936,7 @@ for (let type of ['sync', 'async']) {
)
})
it(`walks ${type} during transformations`, async () => {
test(`walks ${funcType} during transformations`, async () => {
let [visits, visitor] = buildVisitor()
let result = postcss([
visitor,
@@ -954,12 +967,13 @@ for (let type of ['sync', 'async']) {
{ from: 'a.css' }
)
let output
if (type === 'sync') {
if (funcType === 'sync') {
output = result.css
} else {
output = (await result).css
}
expect(output).toEqual(
is(
output,
`a {
color: blue;
}
@@ -974,7 +988,8 @@ for (let type of ['sync', 'async']) {
color: blue;
}`
)
expect(addIndex(visits)).toEqual(
equal(
addIndex(visits),
addIndex([
['Once', '6'],
['Root', '6'],
@@ -1039,7 +1054,7 @@ for (let type of ['sync', 'async']) {
)
})
it(`walks ${type} during transformations in a document`, async () => {
test(`walks ${funcType} during transformations in a document`, async () => {
let document = postcss.document({
nodes: [
postcss.parse(
@@ -1075,13 +1090,14 @@ for (let type of ['sync', 'async']) {
insertFirst
]).process(document, { from: 'a.css' })
let output
if (type === 'sync') {
if (funcType === 'sync') {
output = result.css
} else {
output = (await result).css
}
expect(output).toEqual(
is(
output,
`a {
color: blue;
}
@@ -1096,7 +1112,8 @@ for (let type of ['sync', 'async']) {
color: blue;
}`
)
expect(addIndex(visits)).toEqual(
equal(
addIndex(visits),
addIndex([
['Once', '6'],
['Document', '1'],
@@ -1169,7 +1186,7 @@ for (let type of ['sync', 'async']) {
)
})
it(`has ${type} property and at-rule name filters`, async () => {
test(`has ${funcType} property and at-rule name filters`, async () => {
let filteredDecls: string[] = []
let allDecls: string[] = []
let filteredAtRules: string[] = []
@@ -1209,21 +1226,21 @@ for (let type of ['sync', 'async']) {
`@charset "UTF-8"; @media (screen) { COLOR: black; z-index: 1 }`,
{ from: 'a.css' }
)
if (type === 'sync') {
if (funcType === 'sync') {
result.css
} else {
await result
}
expect(filteredDecls).toEqual(['COLOR'])
expect(allDecls).toEqual(['COLOR', 'z-index'])
expect(filteredExits).toEqual(['COLOR'])
expect(allExits).toEqual(['COLOR', 'z-index'])
expect(filteredAtRules).toEqual(['media'])
expect(allAtRules).toEqual(['charset', 'media'])
equal(filteredDecls, ['COLOR'])
equal(allDecls, ['COLOR', 'z-index'])
equal(filteredExits, ['COLOR'])
equal(allExits, ['COLOR', 'z-index'])
equal(filteredAtRules, ['media'])
equal(allAtRules, ['charset', 'media'])
})
it(`has ${type} property and at-rule name filters in a document`, async () => {
test(`has ${funcType} property and at-rule name filters in a document`, async () => {
let filteredDecls: string[] = []
let allDecls: string[] = []
let filteredAtRules: string[] = []
@@ -1268,21 +1285,21 @@ for (let type of ['sync', 'async']) {
})
let result = postcss([scanner]).process(document, { from: 'a.css' })
if (type === 'sync') {
if (funcType === 'sync') {
result.css
} else {
await result
}
expect(filteredDecls).toEqual(['COLOR'])
expect(allDecls).toEqual(['COLOR', 'z-index'])
expect(filteredExits).toEqual(['COLOR'])
expect(allExits).toEqual(['COLOR', 'z-index'])
expect(filteredAtRules).toEqual(['media'])
expect(allAtRules).toEqual(['charset', 'media'])
equal(filteredDecls, ['COLOR'])
equal(allDecls, ['COLOR', 'z-index'])
equal(filteredExits, ['COLOR'])
equal(allExits, ['COLOR', 'z-index'])
equal(filteredAtRules, ['media'])
equal(allAtRules, ['charset', 'media'])
})
it(`has ${type} OnceExit listener`, async () => {
test(`has ${funcType} OnceExit listener`, async () => {
let rootExit = 0
let OnceExit = 0
@@ -1301,17 +1318,17 @@ for (let type of ['sync', 'async']) {
let result = postcss([plugin]).process('a{}', { from: 'a.css' })
if (type === 'sync') {
if (funcType === 'sync') {
result.css
} else {
await result
}
expect(rootExit).toBe(2)
expect(OnceExit).toBe(1)
is(rootExit, 2)
is(OnceExit, 1)
})
it(`has ${type} OnceExit listener in a document with one root`, async () => {
test(`has ${funcType} OnceExit listener in a document with one root`, async () => {
let RootExit = 0
let OnceExit = 0
let DocumentExit = 0
@@ -1338,18 +1355,18 @@ for (let type of ['sync', 'async']) {
let result = postcss([plugin]).process(document, { from: 'a.css' })
if (type === 'sync') {
if (funcType === 'sync') {
result.css
} else {
await result
}
expect(RootExit).toBe(2)
expect(DocumentExit).toBe(2)
expect(OnceExit).toBe(1)
is(RootExit, 2)
is(DocumentExit, 2)
is(OnceExit, 1)
})
it(`has ${type} OnceExit listener in a document with two roots`, async () => {
test(`has ${funcType} OnceExit listener in a document with two roots`, async () => {
let RootExit = 0
let OnceExit = 0
let DocumentExit = 0
@@ -1376,19 +1393,19 @@ for (let type of ['sync', 'async']) {
let result = postcss([plugin]).process(document, { from: 'a.css' })
if (type === 'sync') {
if (funcType === 'sync') {
result.css
} else {
await result
}
expect(RootExit).toBe(4)
expect(DocumentExit).toBe(2)
expect(OnceExit).toBe(2) // 2 roots === 2 OnceExit
is(RootExit, 4)
is(DocumentExit, 2)
is(OnceExit, 2) // 2 roots === 2 OnceExit
})
}
it('throws error from async OnceExit', async () => {
test('throws error from async OnceExit', async () => {
let plugin: Plugin = {
postcssPlugin: 'test',
OnceExit() {
@@ -1407,17 +1424,17 @@ it('throws error from async OnceExit', async () => {
error = e
}
expect(error.message).toBe('test Exit error')
is(error.message, 'test Exit error')
})
it('rescan Root in another processor', () => {
test('rescan Root in another processor', () => {
let [visits, visitor] = buildVisitor()
let root = postcss([visitor]).process('a{z-index:1}', { from: 'a.css' }).root
visits.splice(0, visits.length)
postcss([visitor]).process(root, { from: 'a.css' }).root
expect(visits).toEqual([
equal(visits, [
['Once', '1'],
['Root', '1'],
['Rule', 'a'],
@@ -1429,7 +1446,7 @@ it('rescan Root in another processor', () => {
])
})
it('rescan Root in another processor in a document', () => {
test('rescan Root in another processor in a document', () => {
let [visits, visitor] = buildVisitor()
let root = postcss([visitor]).process('a{z-index:1}', { from: 'a.css' }).root
let document = postcss.document({ nodes: [root] })
@@ -1437,7 +1454,7 @@ it('rescan Root in another processor in a document', () => {
visits.splice(0, visits.length)
postcss([visitor]).process(document, { from: 'a.css' }).root
expect(visits).toEqual([
equal(visits, [
['Once', '1'],
['Document', '1'],
['Root', '1'],
@@ -1451,7 +1468,7 @@ it('rescan Root in another processor in a document', () => {
])
})
it('marks cleaned nodes as dirty on moving', () => {
test('marks cleaned nodes as dirty on moving', () => {
let mover: Plugin = {
postcssPlugin: 'mover',
Rule(rule) {
@@ -1467,7 +1484,7 @@ it('marks cleaned nodes as dirty on moving', () => {
from: 'a.css'
}).root
expect(visits).toEqual([
equal(visits, [
['Once', '2'],
['Root', '2'],
['Rule', 'a'],
@@ -1489,7 +1506,7 @@ it('marks cleaned nodes as dirty on moving', () => {
])
})
it('marks cleaned nodes as dirty on moving in a document', () => {
test('marks cleaned nodes as dirty on moving in a document', () => {
let mover: Plugin = {
postcssPlugin: 'mover',
Rule(rule) {
@@ -1509,7 +1526,7 @@ it('marks cleaned nodes as dirty on moving in a document', () => {
from: 'a.css'
}).root
expect(visits).toEqual([
equal(visits, [
['Once', '2'],
['Document', '1'],
['Root', '2'],
@@ -1534,3 +1551,5 @@ it('marks cleaned nodes as dirty on moving in a document', () => {
['OnceExit', '1']
])
})
test.run()
Regular → Executable
+30 -26
View File
@@ -1,34 +1,36 @@
import { resolve } from 'path'
import { test } from 'uvu'
import { is, type } from 'uvu/assert'
import { Warning, parse, decl } from '../lib/postcss.js'
it('outputs simple warning', () => {
test('outputs simple warning', () => {
let warning = new Warning('text')
expect(warning.toString()).toBe('text')
is(warning.toString(), 'text')
})
it('outputs warning with plugin', () => {
test('outputs warning with plugin', () => {
let warning = new Warning('text', { plugin: 'plugin' })
expect(warning.toString()).toBe('plugin: text')
is(warning.toString(), 'plugin: text')
})
it('outputs warning with position', () => {
test('outputs warning with position', () => {
let root = parse('a{}')
let warning = new Warning('text', { node: root.first })
expect(warning.toString()).toBe('<css input>:1:1: text')
is(warning.toString(), '<css input>:1:1: text')
})
it('outputs warning with plugin and node', () => {
test('outputs warning with plugin and node', () => {
let file = resolve('a.css')
let root = parse('a{}', { from: file })
let warning = new Warning('text', {
plugin: 'plugin',
node: root.first
})
expect(warning.toString()).toEqual(`plugin: ${file}:1:1: text`)
is(warning.toString(), `plugin: ${file}:1:1: text`)
})
it('outputs warning with index', () => {
test('outputs warning with index', () => {
let file = resolve('a.css')
let root = parse('@rule param {}', { from: file })
let warning = new Warning('text', {
@@ -36,10 +38,10 @@ it('outputs warning with index', () => {
node: root.first,
index: 7
})
expect(warning.toString()).toEqual(`plugin: ${file}:1:8: text`)
is(warning.toString(), `plugin: ${file}:1:8: text`)
})
it('outputs warning with word', () => {
test('outputs warning with word', () => {
let file = resolve('a.css')
let root = parse('@rule param {}', { from: file })
let warning = new Warning('text', {
@@ -47,38 +49,40 @@ it('outputs warning with word', () => {
node: root.first,
word: 'am'
})
expect(warning.toString()).toEqual(`plugin: ${file}:1:10: text`)
is(warning.toString(), `plugin: ${file}:1:10: text`)
})
it('generates warning without source', () => {
test('generates warning without source', () => {
let node = decl({ prop: 'color', value: 'black' })
let warning = new Warning('text', { node })
expect(warning.toString()).toBe('<css input>: text')
is(warning.toString(), '<css input>: text')
})
it('has line and column is undefined by default', () => {
test('has line and column is undefined by default', () => {
let warning = new Warning('text')
expect(warning.line).toBeUndefined()
expect(warning.column).toBeUndefined()
type(warning.line, 'undefined')
type(warning.column, 'undefined')
})
it('gets position from node', () => {
test('gets position from node', () => {
let root = parse('a{}')
let warning = new Warning('text', { node: root.first })
expect(warning.line).toBe(1)
expect(warning.column).toBe(1)
is(warning.line, 1)
is(warning.column, 1)
})
it('gets position from word', () => {
test('gets position from word', () => {
let root = parse('a b{}')
let warning = new Warning('text', { node: root.first, word: 'b' })
expect(warning.line).toBe(1)
expect(warning.column).toBe(3)
is(warning.line, 1)
is(warning.column, 3)
})
it('gets position from index', () => {
test('gets position from index', () => {
let root = parse('a b{}')
let warning = new Warning('text', { node: root.first, index: 2 })
expect(warning.line).toBe(1)
expect(warning.column).toBe(3)
is(warning.line, 1)
is(warning.column, 3)
})
test.run()
Regular → Executable
+237 -1880
View File
File diff suppressed because it is too large Load Diff