lex_test.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package css // import "github.com/tdewolff/parse/css"
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "testing"
  7. "github.com/tdewolff/test"
  8. )
  9. type TTs []TokenType
  10. func TestTokens(t *testing.T) {
  11. var tokenTests = []struct {
  12. css string
  13. expected []TokenType
  14. }{
  15. {" ", TTs{}},
  16. {"5.2 .4", TTs{NumberToken, NumberToken}},
  17. {"color: red;", TTs{IdentToken, ColonToken, IdentToken, SemicolonToken}},
  18. {"background: url(\"http://x\");", TTs{IdentToken, ColonToken, URLToken, SemicolonToken}},
  19. {"background: URL(x.png);", TTs{IdentToken, ColonToken, URLToken, SemicolonToken}},
  20. {"color: rgb(4, 0%, 5em);", TTs{IdentToken, ColonToken, FunctionToken, NumberToken, CommaToken, PercentageToken, CommaToken, DimensionToken, RightParenthesisToken, SemicolonToken}},
  21. {"body { \"string\" }", TTs{IdentToken, LeftBraceToken, StringToken, RightBraceToken}},
  22. {"body { \"str\\\"ing\" }", TTs{IdentToken, LeftBraceToken, StringToken, RightBraceToken}},
  23. {".class { }", TTs{DelimToken, IdentToken, LeftBraceToken, RightBraceToken}},
  24. {"#class { }", TTs{HashToken, LeftBraceToken, RightBraceToken}},
  25. {"#class\\#withhash { }", TTs{HashToken, LeftBraceToken, RightBraceToken}},
  26. {"@media print { }", TTs{AtKeywordToken, IdentToken, LeftBraceToken, RightBraceToken}},
  27. {"/*comment*/", TTs{CommentToken}},
  28. {"/*com* /ment*/", TTs{CommentToken}},
  29. {"~= |= ^= $= *=", TTs{IncludeMatchToken, DashMatchToken, PrefixMatchToken, SuffixMatchToken, SubstringMatchToken}},
  30. {"||", TTs{ColumnToken}},
  31. {"<!-- -->", TTs{CDOToken, CDCToken}},
  32. {"U+1234", TTs{UnicodeRangeToken}},
  33. {"5.2 .4 4e-22", TTs{NumberToken, NumberToken, NumberToken}},
  34. {"--custom-variable", TTs{CustomPropertyNameToken}},
  35. // unexpected ending
  36. {"ident", TTs{IdentToken}},
  37. {"123.", TTs{NumberToken, DelimToken}},
  38. {"\"string", TTs{StringToken}},
  39. {"123/*comment", TTs{NumberToken, CommentToken}},
  40. {"U+1-", TTs{IdentToken, NumberToken, DelimToken}},
  41. // unicode
  42. {"fooδbar􀀀", TTs{IdentToken}},
  43. {"foo\\æ\\†", TTs{IdentToken}},
  44. // {"foo\x00bar", TTs{IdentToken}},
  45. {"'foo\u554abar'", TTs{StringToken}},
  46. {"\\000026B", TTs{IdentToken}},
  47. {"\\26 B", TTs{IdentToken}},
  48. // hacks
  49. {`\-\mo\z\-b\i\nd\in\g:\url(//business\i\nfo.co.uk\/labs\/xbl\/xbl\.xml\#xss);`, TTs{IdentToken, ColonToken, URLToken, SemicolonToken}},
  50. {"width/**/:/**/ 40em;", TTs{IdentToken, CommentToken, ColonToken, CommentToken, DimensionToken, SemicolonToken}},
  51. {":root *> #quince", TTs{ColonToken, IdentToken, DelimToken, DelimToken, HashToken}},
  52. {"html[xmlns*=\"\"]:root", TTs{IdentToken, LeftBracketToken, IdentToken, SubstringMatchToken, StringToken, RightBracketToken, ColonToken, IdentToken}},
  53. {"body:nth-of-type(1)", TTs{IdentToken, ColonToken, FunctionToken, NumberToken, RightParenthesisToken}},
  54. {"color/*\\**/: blue\\9;", TTs{IdentToken, CommentToken, ColonToken, IdentToken, SemicolonToken}},
  55. {"color: blue !ie;", TTs{IdentToken, ColonToken, IdentToken, DelimToken, IdentToken, SemicolonToken}},
  56. // escapes, null and replacement character
  57. {"c\\\x00olor: white;", TTs{IdentToken, ColonToken, IdentToken, SemicolonToken}},
  58. {"null\\0", TTs{IdentToken}},
  59. {"eof\\", TTs{IdentToken}},
  60. {"\"a\x00b\"", TTs{StringToken}},
  61. {"a\\\x00b", TTs{IdentToken}},
  62. {"url(a\x00b)", TTs{BadURLToken}}, // null character cannot be unquoted
  63. {"/*a\x00b*/", TTs{CommentToken}},
  64. // coverage
  65. {" \n\r\n\r\"\\\r\n\\\r\"", TTs{StringToken}},
  66. {"U+?????? U+ABCD?? U+ABC-DEF", TTs{UnicodeRangeToken, UnicodeRangeToken, UnicodeRangeToken}},
  67. {"U+? U+A?", TTs{IdentToken, DelimToken, DelimToken, IdentToken, DelimToken, IdentToken, DelimToken}},
  68. {"-5.23 -moz", TTs{NumberToken, IdentToken}},
  69. {"()", TTs{LeftParenthesisToken, RightParenthesisToken}},
  70. {"url( //url )", TTs{URLToken}},
  71. {"url( ", TTs{URLToken}},
  72. {"url( //url", TTs{URLToken}},
  73. {"url(\")a", TTs{URLToken}},
  74. {"url(a'\\\n)a", TTs{BadURLToken, IdentToken}},
  75. {"url(\"\n)a", TTs{BadURLToken, IdentToken}},
  76. {"url(a h)a", TTs{BadURLToken, IdentToken}},
  77. {"<!- | @4 ## /2", TTs{DelimToken, DelimToken, DelimToken, DelimToken, DelimToken, NumberToken, DelimToken, DelimToken, DelimToken, NumberToken}},
  78. {"\"s\\\n\"", TTs{StringToken}},
  79. {"\"a\\\"b\"", TTs{StringToken}},
  80. {"\"s\n", TTs{BadStringToken}},
  81. // small
  82. {"\"abcd", TTs{StringToken}},
  83. {"/*comment", TTs{CommentToken}},
  84. {"U+A-B", TTs{UnicodeRangeToken}},
  85. {"url((", TTs{BadURLToken}},
  86. {"id\u554a", TTs{IdentToken}},
  87. }
  88. for _, tt := range tokenTests {
  89. t.Run(tt.css, func(t *testing.T) {
  90. l := NewLexer(bytes.NewBufferString(tt.css))
  91. i := 0
  92. for {
  93. token, _ := l.Next()
  94. if token == ErrorToken {
  95. test.T(t, l.Err(), io.EOF)
  96. test.T(t, i, len(tt.expected), "when error occurred we must be at the end")
  97. break
  98. } else if token == WhitespaceToken {
  99. continue
  100. }
  101. test.That(t, i < len(tt.expected), "index", i, "must not exceed expected token types size", len(tt.expected))
  102. if i < len(tt.expected) {
  103. test.T(t, token, tt.expected[i], "token types must match")
  104. }
  105. i++
  106. }
  107. })
  108. }
  109. test.T(t, WhitespaceToken.String(), "Whitespace")
  110. test.T(t, EmptyToken.String(), "Empty")
  111. test.T(t, CustomPropertyValueToken.String(), "CustomPropertyValue")
  112. test.T(t, TokenType(100).String(), "Invalid(100)")
  113. test.T(t, NewLexer(bytes.NewBufferString("x")).consumeBracket(), ErrorToken, "consumeBracket on 'x' must return error")
  114. }
  115. ////////////////////////////////////////////////////////////////
  116. func ExampleNewLexer() {
  117. l := NewLexer(bytes.NewBufferString("color: red;"))
  118. out := ""
  119. for {
  120. tt, data := l.Next()
  121. if tt == ErrorToken {
  122. break
  123. } else if tt == WhitespaceToken || tt == CommentToken {
  124. continue
  125. }
  126. out += string(data)
  127. }
  128. fmt.Println(out)
  129. // Output: color:red;
  130. }