123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- import * as test from 'tape';
-
- import * as AST from '../ast';
- import Compiler, { CompilerOpts } from '../compiler';
- import Env, { EnvError } from '../env';
- import Lexer, { LexerError, LexerResult } from '../lexer';
- import Parser, { ParserError, ParserResult } from '../parser';
- import Token, { TokenTypes } from '../token';
-
- const defaultOpts = {
- prettyPrint: false,
- env: new Env(),
- };
-
- const compile = (source: string, opts: CompilerOpts = defaultOpts)=> {
- const { prettyPrint, env } = opts;
- const lexerResult: LexerResult = new Lexer(source).scan();
- if (!(lexerResult instanceof LexerError)) {
- const parserResult: ParserResult = new Parser(lexerResult).parse();
- if (!(parserResult instanceof ParserError)) {
- return new Compiler(parserResult, { prettyPrint, env }).compile();
- }
-
- return parserResult;
- }
-
- return lexerResult;
- };
-
- test('compiles literal', (t) => {
- t.plan(1);
- const result = compile('#FF0000');
- t.equal(result, '#FF0000');
- });
-
- test('compiles expression', (t) => {
- t.plan(1);
- const result = compile('(div :color blue)');
- t.equal(result, 'div{color:blue;}');
- });
-
- test('compiles expression with pretty printing', (t) => {
- t.plan(1);
- const result = compile('(div :color blue)', { prettyPrint: true });
- t.equal(result, 'div {\n color: blue;\n}');
- });
-
- test('compiles nested expression', (t) => {
- t.plan(1);
- const result = compile('(div :color blue (span :color red))');
- t.equal(result, 'div{color:blue;}div span{color:red;}');
- });
-
- test('omits empty sets', (t) => {
- t.plan(1);
- const result = compile('(div (span :color blue))');
- t.equal(result, 'div span{color:blue;}');
- });
-
- test('pretty printing separates multiple selectors', (t) => {
- t.plan(1);
- const result = compile('(div, p :color blue)', { prettyPrint: true });
- t.equal(result, 'div, p {\n color: blue;\n}');
- });
-
- test('compile identifier', (t) => {
- t.plan(1);
- const result = compile('(@let $red #FF0000 (div :color $red))');
- t.equal(result, 'div{color:#FF0000;}');
- });
-
- test('undefined variable throws an error', (t) => {
- t.plan(1);
- const result = compile('(@let $red #FF0000)(div :color $blue)');
- t.true(result instanceof EnvError);
- });
-
- test('pretty printing does not return an extra newline at the end', (t) => {
- t.plan(1);
- const result = compile('(div :color blue)', { prettyPrint: true });
- t.equal(result, 'div {\n color: blue;\n}');
- });
-
- test('compiles media query', (t) => {
- t.plan(1);
- const result = compile(
- '(@media :min-width 1000px (div :flex-direction row))'
- );
- t.equal(result, '@media(min-width:1000px){div{flex-direction:row;}}');
- });
-
- test('compiles pretty media query', (t) => {
- t.plan(1);
- const result = compile(
- '(@media :min-width 1000px (div :flex-direction row))',
- { prettyPrint: true }
- );
- t.equal(
- result,
- '@media(min-width: 1000px) {\n div {\n flex-direction: row;\n }\n}'
- );
- });
-
- test('compiles keyframe animation', (t) => {
- t.plan(1);
- const result = compile('(@keyframes fade (0% :opacity 1) (100% :opacity 2))');
- t.equal(result, '@keyframes fade{0%{opacity:1;}100%{opacity:2;}}');
- });
-
- test('compiles pretty keyframe animation', (t) => {
- t.plan(1);
- const result = compile(
- '(@keyframes fade (0% :opacity 1) (100% :opacity 2))',
- { prettyPrint: true }
- );
- t.equal(
- result,
- '@keyframes fade {\n 0% {\n opacity: 1;\n }\n 100% {\n opacity: 2;\n }\n}'
- );
- });
-
- test('does not append extra semicolons', (t) => {
- t.plan(1);
- const result = compile('(div :color blue :font-size 16px :display flex)');
- t.equal(result, 'div{color:blue;font-size:16px;display:flex;}');
- });
-
- test('deeply nested rules', (t) => {
- t.plan(1);
- const result = compile('(body (main (div (ul (li (a :color pink))))))');
- t.equal(result, 'body main div ul li a{color:pink;}');
- });
-
- test('compile function applications', (t) => {
- t.plan(2);
- t.equal(compile('(@rgba 1 2 3 4)'), 'rgba(1,2,3,4)');
- t.equal(compile('(div :color (@rgba 1 2 3 4))'), 'div{color:rgba(1,2,3,4);}');
- });
-
- test('identifiers in function applications', (t) => {
- t.plan(1);
- t.equal(compile('(@let $red 30 $green 40 $blue 50 (div :color (@rgb $red $green $blue)))'), 'div{color:rgb(30,40,50);}');
- });
-
- test('ampersand refers to parent selector', (t) => {
- t.plan(1);
- t.equal(compile('(div :color green (&:hover :color blue))'), 'div{color:green;}div:hover{color:blue;}');
- });
-
- test('compiles mixins', (t) => {
- t.plan(2);
- const env = new Env();
- const result = compile('(@mixin centered ($width) :max-width $width :margin auto)', {
- env: env,
- });
- t.equal(result, '');
- t.deepEqual(env.get(new Token(TokenTypes.LITERAL, 'centered', 1)), new AST.Mixin(
- new Token(TokenTypes.LITERAL, 'centered', 1),
- [new AST.Identifier(new Token(TokenTypes.IDENTIFIER, 'width', 1))],
- [
- new AST.Rule(
- new AST.Property(new Token(TokenTypes.PROPERTY, 'max-width', 1)),
- [new AST.Identifier(new Token(TokenTypes.IDENTIFIER, 'width', 1))],
- ),
- new AST.Rule(
- new AST.Property(new Token(TokenTypes.PROPERTY, 'margin', 1)),
- [new AST.Literal(new Token(TokenTypes.LITERAL, 'auto', 1))],
- )
- ]
- ));
- });
-
- test('compiles mixin application', (t) => {
- t.plan(1);
- const result = compile('(@mixin centered ($width) :max-width $width :margin auto)(div (@centered 800px))');
- t.equal(result, 'div{max-width:800px;margin:auto;}');
- });
-
- test('compiles mixin application with children', (t) => {
- t.plan(1);
- const result = compile('(@mixin heading-color ($color) (h1 :color $color))(div (@heading-color blue))');
- t.equal(result, 'div h1{color:blue;}');
- });
-
- test('compiles nested mixins', (t) => {
- t.plan(1);
- const result = compile(`
- (@mixin v-gutters ($v)
- :margin-top $v
- :margin-bottom $v)
-
- (@mixin h-gutters ($h)
- :margin-left $h
- :margin-right $h)
-
- (@mixin gutters ($v $h)
- (@v-gutters $v)
- (@h-gutters $h))
-
- (.container
- (@gutters 50px 100px))
- `);
- t.equal(result, '.container{margin-top:50px;margin-bottom:50px;margin-left:100px;margin-right:100px;}');
- });
-
- test('respects ampersand even if it\'s not the first element of the selector', (t) => {
- t.plan(1);
- const result = compile(`(.foo (.bar & :color blue))`);
- t.equal(result, '.bar .foo{color:blue;}');
- });
|