A stylesheet language written in TypeScript that compiles to CSS
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

compiler.test.ts 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import * as test from 'tape';
  2. import { AST } from '../ast';
  3. import Compiler, { CompilerOpts } from '../compiler';
  4. import Env, { EnvError } from '../env';
  5. import Lexer, { LexerError, LexerResult } from '../lexer';
  6. import Parser, { ParserError, ParserResult } from '../parser';
  7. import Token, { TokenTypes } from '../token';
  8. const defaultOpts = {
  9. prettyPrint: false,
  10. env: new Env(),
  11. };
  12. const compile = (source: string, opts: CompilerOpts = defaultOpts)=> {
  13. const { prettyPrint, env } = opts;
  14. const lexerResult: LexerResult = new Lexer(source).scan();
  15. if (!(lexerResult instanceof LexerError)) {
  16. const parserResult: ParserResult = new Parser(lexerResult).parse();
  17. if (!(parserResult instanceof ParserError)) {
  18. return new Compiler(parserResult, { prettyPrint, env }).compile();
  19. }
  20. return parserResult;
  21. }
  22. return lexerResult;
  23. };
  24. test('compiles literal', (t) => {
  25. t.plan(1);
  26. const result = compile('#FF0000');
  27. t.equal(result, '#FF0000');
  28. });
  29. test('compiles expression', (t) => {
  30. t.plan(1);
  31. const result = compile('(div :color blue)');
  32. t.equal(result, 'div{color:blue;}');
  33. });
  34. test('compiles expression with pretty printing', (t) => {
  35. t.plan(1);
  36. const result = compile('(div :color blue)', { prettyPrint: true });
  37. t.equal(result, 'div {\n color: blue;\n}');
  38. });
  39. test('compiles nested expression', (t) => {
  40. t.plan(1);
  41. const result = compile('(div :color blue (span :color red))');
  42. t.equal(result, 'div{color:blue;}div span{color:red;}');
  43. });
  44. test('omits empty sets', (t) => {
  45. t.plan(1);
  46. const result = compile('(div (span :color blue))');
  47. t.equal(result, 'div span{color:blue;}');
  48. });
  49. test('pretty printing separates multiple selectors', (t) => {
  50. t.plan(1);
  51. const result = compile('(div, p :color blue)', { prettyPrint: true });
  52. t.equal(result, 'div, p {\n color: blue;\n}');
  53. });
  54. test('compile identifier', (t) => {
  55. t.plan(1);
  56. const result = compile('(@let $red #FF0000 (div :color $red))');
  57. t.equal(result, 'div{color:#FF0000;}');
  58. });
  59. test('undefined variable throws an error', (t) => {
  60. t.plan(1);
  61. const result = compile('(@let $red #FF0000)(div :color $blue)');
  62. t.true(result instanceof EnvError);
  63. });
  64. test('pretty printing does not return an extra newline at the end', (t) => {
  65. t.plan(1);
  66. const result = compile('(div :color blue)', { prettyPrint: true });
  67. t.equal(result, 'div {\n color: blue;\n}');
  68. });
  69. test('compiles media query', (t) => {
  70. t.plan(1);
  71. const result = compile(
  72. '(@media :min-width 1000px (div :flex-direction row))'
  73. );
  74. t.equal(result, '@media(min-width:1000px){div{flex-direction:row;}}');
  75. });
  76. test('compiles pretty media query', (t) => {
  77. t.plan(1);
  78. const result = compile(
  79. '(@media :min-width 1000px (div :flex-direction row))',
  80. { prettyPrint: true }
  81. );
  82. t.equal(
  83. result,
  84. '@media(min-width: 1000px) {\n div {\n flex-direction: row;\n }\n}'
  85. );
  86. });
  87. test('compiles keyframe animation', (t) => {
  88. t.plan(1);
  89. const result = compile('(@keyframes fade (0% :opacity 1) (100% :opacity 2))');
  90. t.equal(result, '@keyframes fade{0%{opacity:1;}100%{opacity:2;}}');
  91. });
  92. test('compiles pretty keyframe animation', (t) => {
  93. t.plan(1);
  94. const result = compile(
  95. '(@keyframes fade (0% :opacity 1) (100% :opacity 2))',
  96. { prettyPrint: true }
  97. );
  98. t.equal(
  99. result,
  100. '@keyframes fade {\n 0% {\n opacity: 1;\n }\n 100% {\n opacity: 2;\n }\n}'
  101. );
  102. });
  103. test('does not append extra semicolons', (t) => {
  104. t.plan(1);
  105. const result = compile('(div :color blue :font-size 16px :display flex)');
  106. t.equal(result, 'div{color:blue;font-size:16px;display:flex;}');
  107. });
  108. test('deeply nested rules', (t) => {
  109. t.plan(1);
  110. const result = compile('(body (main (div (ul (li (a :color pink))))))');
  111. t.equal(result, 'body main div ul li a{color:pink;}');
  112. });
  113. test('compile function applications', (t) => {
  114. t.plan(2);
  115. t.equal(compile('(@rgba 1 2 3 4)'), 'rgba(1,2,3,4)');
  116. t.equal(compile('(div :color (@rgba 1 2 3 4))'), 'div{color:rgba(1,2,3,4);}');
  117. });
  118. test('identifiers in function applications', (t) => {
  119. t.plan(1);
  120. t.equal(compile('(@let $red 30 $green 40 $blue 50 (div :color (@rgb $red $green $blue)))'), 'div{color:rgb(30,40,50);}');
  121. });
  122. test('ampersand refers to parent selector', (t) => {
  123. t.plan(1);
  124. t.equal(compile('(div :color green (&:hover :color blue))'), 'div{color:green;}div:hover{color:blue;}');
  125. });
  126. test('compiles mixins', (t) => {
  127. t.plan(2);
  128. const env = new Env();
  129. const result = compile('(@mixin centered ($width) :max-width $width :margin auto)', {
  130. env: env,
  131. });
  132. t.equal(result, '');
  133. t.deepEqual(env.get(new Token(TokenTypes.LITERAL, 'centered', 1)), new AST.Mixin(
  134. new Token(TokenTypes.LITERAL, 'centered', 1),
  135. [new AST.Identifier(new Token(TokenTypes.IDENTIFIER, 'width', 1))],
  136. [
  137. new AST.Rule(
  138. new AST.Property(new Token(TokenTypes.PROPERTY, 'max-width', 1)),
  139. new AST.Identifier(new Token(TokenTypes.IDENTIFIER, 'width', 1)),
  140. ),
  141. new AST.Rule(
  142. new AST.Property(new Token(TokenTypes.PROPERTY, 'margin', 1)),
  143. new AST.Literal(new Token(TokenTypes.LITERAL, 'auto', 1)),
  144. )
  145. ]
  146. ));
  147. });