import { testPath, jsonify, eachTest } from 'postcss-parser-tests' import { readFileSync } from 'fs' import { resolve } from 'path' import { Declaration, AtRule, parse, Root, Rule } from '../lib/postcss.js' it('works with file reads', () => { let stream = readFileSync(testPath('atrule-empty.css')) expect(parse(stream) instanceof Root).toBe(true) }) eachTest((name, css, json) => { it(`parses ${name}`, () => { css = css.replace(/\r\n/g, '\n') let parsed = jsonify(parse(css, { from: name })) expect(JSON.parse(parsed)).toEqual(JSON.parse(json)) }) }) it('parses UTF-8 BOM', () => { let css = parse('\uFEFF@host { a {\f} }') expect(css.nodes[0].raws.before).toEqual('') }) it('should has true at `hasBOM` property', () => { let css = parse('\uFEFF@host { a {\f} }') expect(css.first?.source?.input.hasBOM).toBe(true) }) it('should has false at `hasBOM` property', () => { let css = parse('@host { a {\f} }') expect(css.first?.source?.input.hasBOM).toBe(false) }) it('saves source file', () => { let css = parse('a {}', { from: 'a.css' }) expect(css.first?.source?.input.css).toEqual('a {}') expect(css.first?.source?.input.file).toEqual(resolve('a.css')) expect(css.first?.source?.input.from).toEqual(resolve('a.css')) }) it('keeps absolute path in source', () => { let css = parse('a {}', { from: 'http://example.com/a.css' }) expect(css.first?.source?.input.file).toEqual('http://example.com/a.css') expect(css.first?.source?.input.from).toEqual('http://example.com/a.css') }) it('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')) }) it('sets unique ID for file without name', () => { let css1 = parse('a {}') let css2 = parse('a {}') expect(css1.first?.source?.input.id).toMatch(/^$/) expect(css1.first?.source?.input.from).toMatch(/^$/) expect(css2.first?.source?.input.id).not.toEqual(css1.first?.source?.input.id) }) it('sets parent node', () => { let file = testPath('atrule-rules.css') let css = parse(readFileSync(file)) let support = css.first as AtRule let keyframes = support.first as AtRule 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) }) it('ignores wrong close bracket', () => { let root = parse('a { p: ()) }') let a = root.first as Rule let decl = a.first as Declaration expect(decl.value).toEqual('())') }) it('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).toEqual('--x') }) it('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).toEqual(' :') }) it('parses double semicolon after rule', () => { expect(parse('a { };;').toString()).toEqual('a { };;') }) it('throws on unclosed blocks', () => { expect(() => { parse('\na {\n') }).toThrow(/:2:1: Unclosed block/) }) it('throws on unnecessary block close', () => { expect(() => { parse('a {\n} }') }).toThrow(/:2:3: Unexpected }/) }) it('throws on unclosed comment', () => { expect(() => { parse('\n/*\n ') }).toThrow(/:2:1: Unclosed comment/) }) it('throws on unclosed quote', () => { expect(() => { parse('\n"\n\na ') }).toThrow(/:2:1: Unclosed string/) }) it('throws on unclosed bracket', () => { expect(() => { parse(':not(one() { }') }).toThrow(/:1:5: Unclosed bracket/) }) it('throws on property without value', () => { expect(() => { parse('a { b;}') }).toThrow(/:1:5: Unknown word/) expect(() => { parse('a { b b }') }).toThrow(/:1:5: Unknown word/) }) it('throws on nameless at-rule', () => { expect(() => { parse('@') }).toThrow(/:1:1: At-rule without name/) }) it('throws on property without semicolon', () => { expect(() => { parse('a { one: filter(a:"") two: 2 }') }).toThrow(/:1:21: Missed semicolon/) }) it('throws on double colon', () => { expect(() => { parse('a { one:: 1 }') }).toThrow(/:1:9: Double colon/) }) it('do not throws on comment in between', () => { parse('a { b/* c */: 1 }') }) it('throws on two words in between', () => { expect(() => { parse('a { b c: 1 }') }).toThrow(/:1:7: Unknown word/) }) it('throws on just colon', () => { expect(() => { parse(':') }).toThrow(/:1:1: Unknown word/) expect(() => { parse(' : ') }).toThrow(/:1:2: Unknown word/) }) it('does not suggest different parsers for CSS', () => { let error try { parse('a { one:: 1 }', { from: 'app.css' }) } catch (e) { error = e } expect(error.message).not.toMatch(/postcss-less|postcss-scss/) }) it('suggests postcss-scss for SCSS sources', () => { expect(() => { parse('a { #{var}: 1 }', { from: 'app.scss' }) }).toThrow(/postcss-scss/) }) it('suggests postcss-sass for Sass sources', () => { expect(() => { parse('a\n #{var}: 1', { from: 'app.sass' }) }).toThrow(/postcss-sass/) }) it('suggests postcss-less for Less sources', () => { expect(() => { parse('.@{my-selector} { }', { from: 'app.less' }) }).toThrow(/postcss-less/) })