css-syntax-0.1.0.0: test/Test.hs
{-# LANGUAGE OverloadedStrings, ViewPatterns #-}
module Main where
import qualified Data.Text as T
import Data.Monoid
import Data.CSS.Syntax.Tokens
import Test.Hspec
import Test.Hspec.QuickCheck
import Prelude
import Test.QuickCheck
import Data.Scientific
testTokenize :: HasCallStack => T.Text -> [Token] -> Expectation
testTokenize s t = do
tokenize s `shouldBe` t
tokenize (serialize (tokenize s)) `shouldBe` t
testSerialize :: HasCallStack => [Token] -> T.Text -> Expectation
testSerialize t s = do
serialize t `shouldBe` s
serialize (tokenize (serialize t)) `shouldBe` s
main :: IO ()
main = hspec spec
spec :: Spec
spec = parallel $ do
describe "Data.CSS.Syntax.Tokens" $ do
it "Single Character" $ do
testTokenize "(" [LeftParen]
testTokenize ")" [RightParen]
testTokenize "[" [LeftSquareBracket]
testTokenize "]" [RightSquareBracket]
testTokenize ",," [Comma, Comma]
it "Multiple Character" $ do
testTokenize "~=" [IncludeMatch]
testTokenize "||" [Column]
testTokenize "|||" [Column, Delim '|']
testTokenize "<!--" [CDO]
testTokenize "<!---" [CDO, Delim '-']
testTokenize "<!---->" [CDO, CDC]
testTokenize "<!-- -->" [CDO, Whitespace, CDC]
it "Delimiter" $ do
testTokenize "^" [Delim '^']
testTokenize "|" [Delim '|']
testTokenize "\x7f" [Delim '\x7f']
testTokenize "\1" [Delim '\x1']
testTokenize "~-" [Delim '~', Delim '-']
testTokenize "*^" [Delim '*', Delim '^']
it "Whitespace" $ do
testTokenize " " [Whitespace]
testTokenize "\n\rS" [Whitespace, Ident "S"]
testTokenize " *" [Whitespace, Delim '*']
testTokenize "\n\r\f2" [Whitespace, Number "2" (NVInteger 2)]
it "Escapes" $ do
testTokenize "hel\\6Co" [Ident "hello"]
testTokenize "\\26 B" [Ident "&B"]
testTokenize "'hel\\6c o'" [String "hello"]
testTokenize "'spac\\65\r\ns'" [String "spaces"]
testTokenize "spac\\65\r\ns" [Ident "spaces"]
testTokenize "spac\\65\n\rs" [Ident "space", Whitespace, Ident "s"]
testTokenize "sp\\61\tc\\65\fs" [Ident "spaces"]
testTokenize "hel\\6c o" [Ident "hell", Whitespace, Ident "o"]
testTokenize "test\\\n" [Ident "test", Delim '\\', Whitespace]
testTokenize "test\\D799" [Ident "test\xD799"]
testTokenize "\\E000" [Ident "\xe000"]
testTokenize "te\\s\\t" [Ident "test"]
testTokenize "spaces\\ in\\\tident" [Ident "spaces in\tident"]
testTokenize "\\.\\,\\:\\!" [Ident ".,:!"]
testTokenize "\\\r" [Delim '\\', Whitespace]
testTokenize "\\\f" [Delim '\\', Whitespace]
testTokenize "\\\r\n" [Delim '\\', Whitespace]
-- let replacement = "\xFFFD"
testTokenize "null\\\0" [Ident "null\xfffd"]
testTokenize "null\\\0\0" [Ident $ "null" <> "\xfffd" <> "\xfffd"]
testTokenize "null\\0" [Ident $ "null" <> "\xfffd"]
testTokenize "null\\0000" [Ident $ "null" <> "\xfffd"]
testTokenize "large\\110000" [Ident $ "large" <> "\xfffd"]
testTokenize "large\\23456a" [Ident $ "large" <> "\xfffd"]
testTokenize "surrogate\\D800" [Ident $ "surrogate" <> "\xfffd"]
testTokenize "surrogate\\0DABC" [Ident $ "surrogate" <> "\xfffd"]
testTokenize "\\00DFFFsurrogate" [Ident $ "\xfffd" <> "surrogate"]
testTokenize "\\10fFfF" [Ident "\x10ffff"]
testTokenize "\\10fFfF0" [Ident $ "\x10ffff" <> "0"]
testTokenize "\\10000000" [Ident $ "\x100000" <> "00"]
testTokenize "eof\\" [Ident "eof", Delim '\\']
it "Ident" $ do
testTokenize "simple-ident" [Ident "simple-ident"]
testTokenize "testing123" [Ident "testing123"]
testTokenize "hello!" [Ident "hello", Delim '!']
testTokenize "world\5" [Ident "world", Delim '\5']
testTokenize "_under score" [Ident "_under", Whitespace, Ident "score"]
testTokenize "-_underscore" [Ident "-_underscore"]
testTokenize "-text" [Ident "-text"]
testTokenize "-\\6d" [Ident "-m"]
testTokenize "--abc" [Ident "--abc"]
testTokenize "--" [Ident "--"]
testTokenize "--11" [Ident "--11"]
testTokenize "---" [Ident "---"]
testTokenize "\x2003" [Ident "\x2003"] -- em-space
testTokenize "\xA0" [Ident "\xA0"] -- non-breaking space
testTokenize "\x1234" [Ident "\x1234"]
testTokenize "\x12345" [Ident "\x12345"]
testTokenize "\0" [Ident "\xfffd"]
testTokenize "ab\0c" [Ident $ "ab\xfffd" <> "c"]
it "Function" $ do
testTokenize "scale(2)" [Function "scale", Number "2" (NVInteger 2), RightParen]
testTokenize "foo-bar\\ baz(" [Function "foo-bar baz"]
testTokenize "fun\\(ction(" [Function "fun(ction"]
testTokenize "-foo(" [Function "-foo"]
testTokenize "url(\"foo.gif\"" [Function "url", String "foo.gif"]
testTokenize "foo( \'bar.gif\'" [Function "foo", Whitespace, String "bar.gif"]
-- // To simplify implementation we drop the whitespace in function(url),whitespace,string()
testTokenize "url( \'bar.gif\'" [Function "url", String "bar.gif"]
it "AtKeyword" $ do
testTokenize "@at-keyword" [AtKeyword "at-keyword"]
testTokenize "@hello!" [AtKeyword "hello", Delim '!']
testTokenize "@-text" [AtKeyword "-text"]
testTokenize "@--abc" [AtKeyword "--abc"]
testTokenize "@--" [AtKeyword "--"]
testTokenize "@--11" [AtKeyword "--11"]
testTokenize "@---" [AtKeyword "---"]
testTokenize "@\\ " [AtKeyword " "]
testTokenize "@-\\ " [AtKeyword "- "]
testTokenize "@@" [Delim '@', Delim '@']
testTokenize "@2" [Delim '@', Number "2" (NVInteger 2)]
testTokenize "@-1" [Delim '@', Number "-1" (NVInteger (-1))]
it "Url" $ do
testTokenize "url(foo.gif)" [Url "foo.gif"]
testTokenize "urL(https://example.com/cats.png)" [Url "https://example.com/cats.png"]
testTokenize "uRl(what-a.crazy^URL~this\\ is!)" [Url "what-a.crazy^URL~this is!"]
testTokenize "uRL(123#test)" [Url "123#test"]
testTokenize "Url(escapes\\ \\\"\\'\\)\\()" [Url "escapes \"')("]
testTokenize "UrL( whitespace )" [Url "whitespace"]
testTokenize "URl( whitespace-eof " [Url "whitespace-eof"]
testTokenize "URL(eof" [Url "eof"]
testTokenize "url(not/*a*/comment)" [Url "not/*a*/comment"]
testTokenize "urL()" [Url ""]
testTokenize "uRl(white space)," [BadUrl, Comma]
testTokenize "Url(b(ad)," [BadUrl, Comma]
testTokenize "uRl(ba'd):" [BadUrl, Colon]
testTokenize "urL(b\"ad):" [BadUrl, Colon]
testTokenize "uRl(b\"ad):" [BadUrl, Colon]
testTokenize "Url(b\\\rad):" [BadUrl, Colon]
testTokenize "url(b\\\nad):" [BadUrl, Colon]
testTokenize "url(/*'bad')*/" [BadUrl, Delim '*', Delim '/']
testTokenize "url(ba'd\\\\))" [BadUrl, RightParen]
it "String" $ do
testTokenize "'text'" [String "text"]
testTokenize "\"text\"" [String "text"]
testTokenize "'testing, 123!'" [String "testing, 123!"]
testTokenize "'es\\'ca\\\"pe'" [String "es'ca\"pe"]
testTokenize "'\"quotes\"'" [String "\"quotes\""]
testTokenize "\"'quotes'\"" [String "'quotes'"]
testTokenize "\"mismatch'" [String "mismatch'"]
testTokenize "'text\5\t\xb'" [String "text\5\t\xb"]
testTokenize "\"end on eof" [String "end on eof"]
testTokenize "'esca\\\nped'" [String "escaped"]
testTokenize "\"esc\\\faped\"" [String "escaped"]
testTokenize "'new\\\rline'" [String "newline"]
testTokenize "\"new\\\r\nline\"" [String "newline"]
testTokenize "'bad\nstring" [BadString, Whitespace, Ident "string"]
testTokenize "'bad\rstring" [BadString, Whitespace, Ident "string"]
testTokenize "'bad\r\nstring" [BadString, Whitespace, Ident "string"]
testTokenize "'bad\fstring" [BadString, Whitespace, Ident "string"]
testTokenize "'\0'" [String "\xFFFD"]
testTokenize "'hel\0lo'" [String "hel\xfffdlo"]
testTokenize "'h\\65l\0lo'" [String "hel\xfffdlo"]
testTokenize "'ignore backslash at eof\\" [String "ignore backslash at eof"]
it "Hash" $ do
testTokenize "#id-selector" [Hash HId "id-selector"]
testTokenize "#FF7700" [Hash HId "FF7700"]
testTokenize "#3377FF" [Hash HUnrestricted "3377FF"]
testTokenize "#\\ " [Hash HId " "]
testTokenize "# " [Delim '#', Whitespace]
testTokenize "#\\\n" [Delim '#', Delim '\\', Whitespace]
testTokenize "#\\\r\n" [Delim '#', Delim '\\', Whitespace]
testTokenize "#!" [Delim '#', Delim '!']
it "Number" $ do
testTokenize "10" [Number "10" (NVInteger 10)]
testTokenize "12.0" [Number "12.0" (NVNumber 12)]
testTokenize "+45.6" [Number "+45.6" (NVNumber 45.6)]
testTokenize "-7" [Number "-7" (NVInteger (-7))]
testTokenize "010" [Number "010" (NVInteger 10)]
testTokenize "10e0" [Number "10e0" (NVNumber 10)]
testTokenize "12e3" [Number "12e3" (NVNumber 12000)]
testTokenize "3e+1" [Number "3e+1" (NVNumber 30)]
testTokenize "12E-1" [Number "12E-1" (NVNumber 1.2)]
testTokenize ".7" [Number ".7" (NVNumber 0.7)]
testTokenize "-.3" [Number "-.3" (NVNumber (-0.3))]
testTokenize "+637.54e-2" [Number "+637.54e-2" (NVNumber 6.3754)]
testTokenize "-12.34E+2" [Number "-12.34E+2" (NVNumber (-1234))]
testTokenize "+ 5" [Delim '+', Whitespace, Number "5" (NVInteger 5)]
testTokenize "-+12" [Delim '-', Number "+12" (NVInteger 12)]
testTokenize "+-21" [Delim '+', Number "-21" (NVInteger (-21))]
testTokenize "++22" [Delim '+', Number "+22" (NVInteger 22)]
testTokenize "13." [Number "13" (NVInteger 13), Delim ('.')]
testTokenize "1.e2" [Number "1" (NVInteger 1), Delim '.', Ident "e2"]
testTokenize "2e3.5" [Number "2e3" (NVNumber 2e3), Number ".5" (NVNumber 0.5)]
testTokenize "2e3." [Number "2e3" (NVNumber 2e3), Delim '.']
testTokenize "1000000000000000000000000" [Number "1000000000000000000000000" (NVInteger 1000000000000000000000000)]
testTokenize "123456789223456789323456789423456789523456789623456789723456789823456789923456789" [Number "123456789223456789323456789423456789523456789623456789723456789823456789923456789" (NVInteger 123456789223456789323456789423456789523456789623456789723456789823456789923456789)]
testTokenize "1.797693134862315708145274237317043567980705675258449965989174768031572607800285387605895586327668781715404589535143824642e308" [Number "1.797693134862315708145274237317043567980705675258449965989174768031572607800285387605895586327668781715404589535143824642e308" (NVNumber 1.797693134862315708145274237317043567980705675258449965989174768031572607800285387605895586327668781715404589535143824642e308)]
it "Dimension" $ do
testTokenize "10px" [Dimension "10" (NVInteger 10) "px"]
testTokenize "12.0em" [Dimension "12.0" (NVNumber 12) "em"]
testTokenize "-12.0em" [Dimension "-12.0" (NVNumber (-12)) "em"]
testTokenize "+45.6__qem" [Dimension "+45.6" (NVNumber 45.6) "__qem"]
testTokenize "5e" [Dimension "5" (NVInteger 5) "e"]
testTokenize "5px-2px" [Dimension "5" (NVInteger 5) "px-2px"]
testTokenize "5e-" [Dimension "5" (NVInteger 5) "e-"]
testTokenize "5\\ " [Dimension "5" (NVInteger 5) " "]
testTokenize "40\\70\\78" [Dimension "40" (NVInteger 40) "px"]
testTokenize "4e3e2" [Dimension "4e3" (NVNumber 4e3) "e2"]
testTokenize "0x10px" [Dimension "0" (NVInteger 0) "x10px"]
testTokenize "4unit " [Dimension "4" (NVInteger 4) "unit", Whitespace]
testTokenize "5e+" [Dimension "5" (NVInteger 5) "e", Delim '+']
testTokenize "2e.5" [Dimension "2" (NVInteger 2) "e", Number ".5" (NVNumber 0.5)]
testTokenize "2e+.5" [Dimension "2" (NVInteger 2) "e", Number "+.5" (NVNumber 0.5)]
testTokenize "1-2" [Number "1" (NVInteger 1), Number "-2" (NVInteger (-2))]
testTokenize "1\\65 1" [Dimension "1" (NVInteger 1) "e1"]
testTokenize "1\\31 em" [Dimension "1" (NVInteger 1) "1em"]
testTokenize "1e\\31 em" [Dimension "1" (NVInteger 1) "e1em"]
it "Percentage" $ do
testTokenize "10%" [Percentage "10" $ NVInteger 10]
testTokenize "+12.0%" [Percentage "+12.0" $ NVNumber 12.0]
testTokenize "-48.99%" [Percentage "-48.99" $ NVNumber (-48.99)]
testTokenize "6e-1%" [Percentage "6e-1" $ NVNumber 6e-1]
testTokenize "5%%" [Percentage "5" (NVInteger 5), Delim '%']
-- 402 TEST(CSSTokenizerTest, UnicodeRangeToken)
-- 403 {
-- 404 TEST_TOKENS("u+012345-123456", unicodeRange(0x012345, 0x123456));
-- 405 TEST_TOKENS("U+1234-2345", unicodeRange(0x1234, 0x2345));
-- 406 TEST_TOKENS("u+222-111", unicodeRange(0x222, 0x111));
-- 407 TEST_TOKENS("U+CafE-d00D", unicodeRange(0xcafe, 0xd00d));
-- 408 TEST_TOKENS("U+2??", unicodeRange(0x200, 0x2ff));
-- 409 TEST_TOKENS("U+ab12??", unicodeRange(0xab1200, 0xab12ff));
-- 410 TEST_TOKENS("u+??????", unicodeRange(0x000000, 0xffffff));
-- 411 TEST_TOKENS("u+??", unicodeRange(0x00, 0xff));
-- 412
-- 413 TEST_TOKENS("u+222+111", unicodeRange(0x222, 0x222), number(IntegerValueType, 111, PlusSign));
-- 414 TEST_TOKENS("u+12345678", unicodeRange(0x123456, 0x123456), number(IntegerValueType, 78, NoSign));
-- 415 TEST_TOKENS("u+123-12345678", unicodeRange(0x123, 0x123456), number(IntegerValueType, 78, NoSign));
-- 416 TEST_TOKENS("u+cake", unicodeRange(0xca, 0xca), ident("ke"));
-- 417 TEST_TOKENS("u+1234-gggg", unicodeRange(0x1234, 0x1234), ident("-gggg"));
-- 418 TEST_TOKENS("U+ab12???", unicodeRange(0xab1200, 0xab12ff), delim('?'));
-- 419 TEST_TOKENS("u+a1?-123", unicodeRange(0xa10, 0xa1f), number(IntegerValueType, -123, MinusSign));
-- 420 TEST_TOKENS("u+1??4", unicodeRange(0x100, 0x1ff), number(IntegerValueType, 4, NoSign));
-- 421 TEST_TOKENS("u+z", ident("u"), delim('+'), ident("z"));
-- 422 TEST_TOKENS("u+", ident("u"), delim('+'));
-- 423 TEST_TOKENS("u+-543", ident("u"), delim('+'), number(IntegerValueType, -543, MinusSign));
it "Comment" $ do
testTokenize "/*comment*/a" [Ident "a"]
testTokenize "/**\\2f**//" [Delim '/']
testTokenize "/**y*a*y**/ " [Whitespace]
testTokenize ",/* \n :) \n */)" [Comma, RightParen]
testTokenize ":/*/*/" [Colon]
testTokenize "/**/*" [Delim '*']
testTokenize ";/******" [Semicolon]
it "Serialization" $ do
testSerialize [String "hello,\x0world"] "\"hello,\xFFFDworld\""
testSerialize [String "\x10\x7f\""] "\"\\10 \\7f \\\"\""
testSerialize [String "\xABCDE"] "\"\xABCDE\""
testSerialize [Ident "-"] "\\-"
testSerialize [Ident "5"] "\\35 "
testSerialize [Ident "-5"] "-\\35 "
testSerialize [Ident "-9f\\oo()"] "-\\39 f\\\\oo\\(\\)"
testSerialize [Ident "\xABCDE"] "\xABCDE"
testSerialize [Ident "a", Ident "b"] "a/**/b"
testSerialize [Dimension "1" (NVInteger 1) "em", Ident "b"] "1em/**/b"
modifyMaxSize (const 500) $ modifyMaxSuccess (const 100000) $
prop "Tokeninze=>serialize=>tokenize roundtrip"
prop_tstRoundTrip
modifyMaxSize (const 50) $ modifyMaxSuccess (const 100000) $
prop "Serialize=>tokenize roundtrip"
prop_stRoundTrip
prop_tstRoundTrip :: String -> Bool
prop_tstRoundTrip s = tokenize (serialize t) == t
where t = tokenize $ T.pack s
prop_stRoundTrip :: TestTokens -> Bool
prop_stRoundTrip (TestTokens t) = tokenize (serialize t) == t
newtype TestTokens = TestTokens [Token]
deriving (Show, Eq)
instance Arbitrary TestTokens where
arbitrary = TestTokens . go <$> arbitrary
where go [] = []
go (x : xs@(Whitespace : _)) | needWhitespace x = x : go xs
-- we can only have [BadString, Whitespace]
-- or [Delim '\\', Whitespace] sequences
go (x : xs) | needWhitespace x = x : Whitespace : go xs
go (x@(Function (T.toLower -> "url")) : xs) =
x : String "argument" : go xs
go (x : xs) = x : go xs
needWhitespace x = case x of
BadString -> True
Delim '\\' -> True
_ -> False
instance Arbitrary Token where
arbitrary = oneof $
map return
[ Whitespace
, CDO
, CDC
, Comma
, Colon
, Semicolon
, LeftParen
, RightParen
, LeftSquareBracket
, RightSquareBracket
, LeftCurlyBracket
, RightCurlyBracket
, SuffixMatch
, SubstringMatch
, PrefixMatch
, DashMatch
, IncludeMatch
, Column
, BadString
, BadUrl
]
++
[ String <$> text
, num Number
, num Percentage
, num Dimension <*> ident
, Url <$> text
, Ident <$> ident
, AtKeyword <$> ident
, Function <$> ident
, return $ Function "url"
, Hash HId <$> ident
, Delim <$> elements possibleDelimiters
]
where ident = notEmpty text
notEmpty x = do
r <- x
if r /= "" then return r else notEmpty x
text = T.replace "\NUL" "\xFFFD" . T.pack <$> arbitrary
num token = do
c <- arbitrary
e <- arbitrary
let (t, n)
| e /= 0 = nv NVNumber (scientific c e)
| otherwise = nv NVInteger c
nv f x = (T.pack $ show x, f x)
return $ token t n
possibleDelimiters =
[d | c <- ['\0'..'\xff']
, [Delim d] <- [tokenize (T.pack [c])]]
++ ['\\']