hasmin-0.3.2: tests/Hasmin/Types/DeclarationSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Hasmin.Types.DeclarationSpec where
import Data.Foldable (traverse_)
import Data.Monoid ((<>))
import Data.Text (Text)
import Hasmin.Parser.Internal
import Hasmin.TestUtils
import Hasmin.Types.Class
import Hasmin.Types.Declaration
declarationTests :: Spec
declarationTests =
describe "declaration" $
mapM_ (matchSpec declaration) declarationTestsInfo
propertySpecificTests :: Spec
propertySpecificTests =
describe "property specific changes" $
mapM_ (matchSpec f) propertySpecificTestsInfo
where f = minify <$> declaration
propertySpecificTestsInfo :: [(Text, Text)]
propertySpecificTestsInfo =
[("font-family:Arial Black Long Name", "font-family:arial black long name")
,("font:9px Arial Black Long Name", "font:9px arial black long name")
,("font-family:\"SANS-SERIF\"", "font-family:\"sans-serif\"")
,("font-family:'Helvetica','Arial',sans-serif", "font-family:helvetica,arial,sans-serif")
-- TODO make this work
-- should fallback and parse till ';'
-- ,("font-size:1em\nline-height:1.38;", "font-size:1em\nline-height:1.38;")
,("background-size:1px auto, 2px auto", "background-size:1px,2px")
,("text-shadow: 1px 1px 1px #ff0000", "text-shadow:1px 1px 1px red")
,("text-shadow: 12px 12px 12px", "text-shadow:9pt 9pt 9pt")
,("text-shadow: 1px 1px 0 green", "text-shadow:1px 1px green")
,("text-shadow: 1px 1px 0 green, 2px 2px 0 blue", "text-shadow:1px 1px green,2px 2px blue")
,("font-weight:normal", "font-weight:400")
,("font-weight:bold", "font-weight:700")
,("word-spacing:normal", "word-spacing:0")
,("word-spacing:initial", "word-spacing:0")
,("word-spacing:unset", "word-spacing:unset")
,("word-spacing:inherit", "word-spacing:unset")
,("vertical-align:baseline", "vertical-align:0")
,("vertical-align:initial", "vertical-align:0")
,("vertical-align:unset", "vertical-align:0")
,("transform-origin:left top", "transform-origin:0 0")
,("transform-origin:left 0", "transform-origin:0 0")
,("transform-origin:0 top", "transform-origin:0 0")
,("transform-origin:0 0", "transform-origin:0 0")
,("transform-origin:top left", "transform-origin:0 0")
,("transform-origin:top 0", "transform-origin:0 0")
,("transform-origin:0 left", "transform-origin:0 0")
,("transform-origin:left bottom", "transform-origin:0 100%")
,("transform-origin:0 bottom", "transform-origin:0 100%")
,("transform-origin:left 100%", "transform-origin:0 100%")
,("transform-origin:0 100%", "transform-origin:0 100%")
,("transform-origin:bottom left", "transform-origin:0 100%")
,("transform-origin:100% left", "transform-origin:0 100%")
,("transform-origin:bottom 0", "transform-origin:0 100%")
,("transform-origin:right top", "transform-origin:100% 0")
,("transform-origin:100% top", "transform-origin:100% 0")
,("transform-origin:right 0", "transform-origin:100% 0")
,("transform-origin:top 100%", "transform-origin:100% 0")
,("transform-origin:0% right", "transform-origin:100% 0")
,("transform-origin:top right", "transform-origin:100% 0")
,("transform-origin:right bottom", "transform-origin:100% 100%")
,("transform-origin:100% bottom", "transform-origin:100% 100%")
,("transform-origin:right 100%", "transform-origin:100% 100%")
,("transform-origin:bottom 100%", "transform-origin:100% 100%")
,("transform-origin:100% right", "transform-origin:100% 100%")
,("transform-origin:bottom right", "transform-origin:100% 100%")
,("transform-origin:center bottom", "transform-origin:bottom")
,("transform-origin:50% bottom", "transform-origin:bottom")
,("transform-origin:center 100%", "transform-origin:bottom")
,("transform-origin:bottom 50%", "transform-origin:bottom")
,("transform-origin:bottom center", "transform-origin:bottom")
,("transform-origin:center top", "transform-origin:top")
,("transform-origin:50% top", "transform-origin:top")
,("transform-origin:center 0px", "transform-origin:top")
,("transform-origin:top 50%", "transform-origin:top")
,("transform-origin:top center", "transform-origin:top")
,("transform-origin:center right", "transform-origin:100%")
,("transform-origin:50% right", "transform-origin:100%")
,("transform-origin:right 50%", "transform-origin:100%")
,("transform-origin:100% center", "transform-origin:100%")
,("transform-origin:right center", "transform-origin:100%")
,("transform-origin:center left", "transform-origin:0")
,("transform-origin:50% left", "transform-origin:0")
,("transform-origin:left 50%", "transform-origin:0")
,("transform-origin:0% center", "transform-origin:0")
,("transform-origin:left center", "transform-origin:0")
,("transform-origin:center", "transform-origin:50%")
,("transform-origin:center center", "transform-origin:50%")
,("transform-origin:center 50%", "transform-origin:50%")
,("transform-origin:50% center", "transform-origin:50%")
,("transform-origin:50% 50%", "transform-origin:50%")
,("transform-origin:center center 0", "transform-origin:50%")
,("transform-origin:center center 1px", "transform-origin:50% 50% 1px")
,("transform-origin:left 30px", "transform-origin:0 30px")
,("transform-origin:30px left", "transform-origin:0 30px")
,("transform-origin:right 30px", "transform-origin:100% 30px")
,("transform-origin:30px right", "transform-origin:100% 30px")
,("transform-origin:bottom 30px", "transform-origin:30px 100%")
,("transform-origin:30px bottom", "transform-origin:30px 100%")
,("transform-origin:top 30px", "transform-origin:30px 0")
,("transform-origin:30px top", "transform-origin:30px 0")
,("transform-origin:center 30px", "transform-origin:50% 30px")
,("transform-origin:30px center", "transform-origin:30px")
-- 3 value syntax
,("transform-origin:left top 1px", "transform-origin:0 0 1px")
,("transform-origin:top left 1px", "transform-origin:0 0 1px")
,("transform-origin:left bottom 1px", "transform-origin:0 100% 1px")
,("transform-origin:bottom left 1px", "transform-origin:0 100% 1px")
,("transform-origin:right top 1px", "transform-origin:100% 0 1px")
,("transform-origin:top right 1px", "transform-origin:100% 0 1px")
,("transform-origin:right bottom 1px", "transform-origin:100% 100% 1px")
,("transform-origin:bottom right 1px", "transform-origin:100% 100% 1px")
,("transform-origin:center bottom 1px", "transform-origin:50% 100% 1px")
,("transform-origin:bottom center 1px", "transform-origin:50% 100% 1px")
,("transform-origin:center top 1px", "transform-origin:50% 0 1px")
,("transform-origin:top center 1px", "transform-origin:50% 0 1px")
,("transform-origin:center right 1px", "transform-origin:100% 50% 1px")
,("transform-origin:right center 1px", "transform-origin:100% 50% 1px")
,("transform-origin:center left 1px", "transform-origin:0 50% 1px")
,("transform-origin:left center 1px", "transform-origin:0 50% 1px")
]
declarationTestsInfo :: [(Text, Text)]
declarationTestsInfo =
[("box-shadow:0 7px 0 #fefefe, 0 14px 0 #fefefe",
"box-shadow:0 7px 0 #fefefe,0 14px 0 #fefefe")
]
cleaningTests :: Spec
cleaningTests =
describe "cleaning function" $
mapM_ (matchSpecWithDesc f) cleaningTestsInfo
where f = (Declarations . clean) <$> declarations
cleaningTestsInfo :: [(String, Text, Text)]
cleaningTestsInfo =
[("Merge margin with later margin-top",
"margin:0;margin-top:5px",
"margin:5px 0 0")
,("Merge margin with later margin-right",
"margin:2px;margin-right:1in",
"margin:2px 1in 2px 2px")
,("Merge margin with later margin-bottom",
"margin:auto;margin-bottom:.2em",
"margin:auto auto .2em")
,("Merge margin with later margin-left",
"margin:fill;margin-left:9px",
"margin:fill fill fill 9px")
,("Merge margin with later margin-top, with declaration in-between",
"margin:0;color:red;margin-top:5px",
"margin:5px 0 0;color:red")
,("Merge margin with later margin-right, with declaration in-between",
"margin:2px;color:red;margin-right:1in",
"margin:2px 1in 2px 2px;color:red")
,("Merge margin with later margin-bottom, with declaration in-between",
"margin:auto;color:red;margin-bottom:.2em",
"margin:auto auto .2em;color:red")
,("Merge margin with later margin-left, with declaration in-between",
"margin:fill;color:red;margin-left:9px",
"margin:fill fill fill 9px;color:red")
,("Drop a margin-top overwritten by a following margin",
"margin-top:5px;margin:0",
"margin:0")
,("Drop a margin-right overwritten by a following margin",
"margin-right:1px;margin:2px",
"margin:2px")
,("Drop a margin-bottom overwritten by a following margin",
"margin-bottom:.2em;margin:auto",
"margin:auto")
,("Drop a margin-left overwritten by a following margin",
"margin-left:9px;margin:fill",
"margin:fill")
,("Drop a margin-top overwritten by later margin, with a declaration in-between",
"margin-top:5px;padding:0;margin:0",
"padding:0;margin:0")
,("Drop a margin-right overwritten by later margin, with a declaration in-between",
"margin-right:1px;padding:0;margin:2px",
"padding:0;margin:2px")
,("Drop a margin-bottom overwritten by later margin, with a declaration in-between",
"margin-bottom:.2em;padding:0;margin:auto",
"padding:0;margin:auto")
,("Drop a margin-left overwritten by later margin, with a declaration in-between",
"margin-left:9px;padding:0;margin:fill",
"padding:0;margin:fill")
,("Drop a margin-top overwritten by a later margin-top, with a declaration in-between",
"margin-top:99px;color:red;margin-top:0",
"color:red;margin-top:0")
,("Drop a margin-right overwritten by a later margin-right, with a declaration in-between",
"margin-right:99px;color:red;margin-right:0",
"color:red;margin-right:0")
,("Drop a margin-bottom overwritten by a later margin-bottom, with a declaration in-between",
"margin-bottom:99px;color:red;margin-bottom:0",
"color:red;margin-bottom:0")
,("Drop a margin-left overwritten by a later margin-left, with a declaration in-between",
"margin-left:99px;color:red;margin-left:0",
"color:red;margin-left:0")
,("Drop a margin-top overwritten by a previous margin-top with !important",
"margin-top:99px!important;margin-top:0",
"margin-top:99px!important")
,("Drop a margin-right overwritten by a previous margin-right with !important",
"margin-right:99px!important;margin-right:0",
"margin-right:99px!important")
,("Drop a margin-bottom overwritten by a previous margin-bottom with !important",
"margin-bottom:99px!important;margin-bottom:0",
"margin-bottom:99px!important")
,("Drop a margin-left overwritten by a previous margin-left with !important",
"margin-left:99px!important;margin-left:0",
"margin-left:99px!important")
,("Drop a margin-top overwritten by a previous margin-top with !important, with a declaration in-between",
"margin-top:99px!important;margin:auto;margin-top:0",
"margin-top:99px!important;margin:auto")
,("Drop a margin-right overwritten by a previous margin-right with !important, with a declaration in-between",
"margin-right:99px!important;margin:auto;margin-right:0",
"margin-right:99px!important;margin:auto")
,("Drop a margin-bottom overwritten by a previous margin-bottom with !important, with a declaration in-between",
"margin-bottom:99px!important;margin:auto;margin-bottom:0",
"margin-bottom:99px!important;margin:auto")
,("Drop a margin-left overwritten by a previous margin-left with !important, with a declaration in-between",
"margin-left:99px!important;margin:auto;margin-left:0",
"margin-left:99px!important;margin:auto")
-- ,("When merging, translate initial keyword to the initial value",
-- "margin:0;margin-top:initial",
-- "margin:0")
-- ,("When merging, translate unset keyword of a property that inherits to the initial value",
-- "margin:0;margin-top:unset",
-- "margin:0")
-- ,("Don't merge properties with inherit keyword",
-- "margin:0;margin-top:inherit",
-- "margin:0;margin-top:inherit")
]
minifyDecTests :: Spec
minifyDecTests =
describe "minifyDec function" $
mapM_ (matchSpec f) minifyDecTestsInfo
where f = minify <$> declaration
shorthandInitialValuesTests :: Spec
shorthandInitialValuesTests = do
testMatches "background minification" backgroundTestsInfo
testMatches "transition minification" transitionTestsInfo
testMatches "animation minification" animationTestsInfo
testMatches "font minification" fontTestsInfo
testMatches "outline minification" outlineTestsInfo
anyOrderShorthandTests
where testMatches a i = describe a $ mapM_ (matchSpecWithDesc (minify <$> declaration)) i
backgroundTestsInfo :: [(String, Text, Text)]
backgroundTestsInfo =
[("Does not remove <position> from background when the <bg-size> cannot be removed",
"background: 0 0 / 3px", "background:0 0/3px")
,("Removes <position> when it is the default and no <bg-size> is present",
"background: #fff 0 0", "background:#fff")
,("Removes both <position> and <bg-size> when they are the default",
"background: #fff 0 0/auto", "background:#fff")
,("Removes background-clip keyword when it equals the background-origin keyword",
"background: border-box #fff border-box", "background:border-box #fff")
,("Removes the keywords for background-origin and background-clip when both are the initial ones",
"background: #fff padding-box border-box", "background:#fff")
,("Removes the background-attachment keyword when it is the initial one",
"background: #fff scroll", "background:#fff")
,("Removes the background-color value when it is the initial one",
"background: url(img.png) transparent", "background:url(img.png)")
,("Removes background-image when it is the default (i.e. none)",
"background: none #fff", "background:#fff")
,("Removes background-repeat when it is the default (i.e. repeat)",
"background: repeat #fff", "background:#fff")
]
transitionTestsInfo :: [(String, Text, Text)]
transitionTestsInfo =
[("Removes transition-duration when it is the default",
"transition: ease-out some-property 0s", "transition:ease-out some-property")
,("Removes transition-delay when it is the default",
"transition: ease-out 1s some-property 0s", "transition:ease-out 1s some-property")
,("Leaves an initial (i.e. 0s) transition-duration when the transition-delay is not initial (i.e. also 0s)",
"transition: all 0s 1s ease", "transition:0s 1s")
,("Removes transition-timing-function when it is the default",
"transition: some-property 1s ease", "transition:1s some-property")
,("Removes transition-property when it is the default",
"transition: ease-out all 1s", "transition:ease-out 1s")
,("Leaves only 0s when every present value is initial",
"transition: all 0s 0s ease", "transition:0s")
]
animationTestsInfo :: [(String, Text, Text)]
animationTestsInfo =
[("Removes default animation-fill-mode when the animation-name doesn't overlap with its keywords",
"animation:none animName", "animation:animName")
,("Leaves default animation-fill-mode when the animation-name overlaps with one of its keywords",
"animation:none backwards", "animation:none backwards")
,("Removes default animation-fill-mode and default animation-name when both are used simultaneously",
"animation:ease-out none none", "animation:ease-out")
,("Removes default animation-timing-function",
"animation:animName ease", "animation:animName")
,("Removes default animation-duration",
"animation:0s ease-out", "animation:ease-out")
,("Removes default animation-delay",
"animation:1s 0s ease-out", "animation:1s ease-out")
,("Leaves default animation-duration when animation-delay is not the default",
"animation:0s 1s ease-out", "animation:0s 1s ease-out")
,("Removes default animation-iteration-count",
"animation:1 ease-out", "animation:ease-out")
,("Removes default animation-direction when the animation-name doesn't overlap with its keywords",
"animation:normal animName", "animation:animName")
,("Leaves default animation-direction when the animation-name overlaps with one of its keywords",
"animation:normal alternate", "animation:normal alternate")
,("Removes default animation-play-state when the animation-name doesn't overlap with its keywords",
"animation:running animName", "animation:animName")
,("Leaves default animation-play-state when the animation-name overlaps with one of its keywords",
"animation:running paused", "animation:running paused")
]
fontTestsInfo :: [(String, Text, Text)]
fontTestsInfo =
[("Removes default font-style",
"font:normal condensed bolder small-caps 9px/1 sans", "font:small-caps bolder condensed 9px/1 sans")
,("Removes default font-variant",
"font:normal italic condensed bolder 9px/1 sans", "font:italic bolder condensed 9px/1 sans")
,("Removes default font-weight",
"font:italic normal condensed small-caps 9px/1 sans", "font:italic small-caps condensed 9px/1 sans")
,("Converts bold font-weight into 700",
"font:bold 9px sans", "font:700 9px sans")
,("Removes default font-stretch",
"font:italic normal small-caps bolder 9px/1 sans", "font:italic small-caps bolder 9px/1 sans")
,("Removes default line-height",
"font:9px/normal sans", "font:9px sans")
,("Lowercases and removes quotes from font-family values",
"font:9px 'Arial Black', \"sans-serif\", sans", "font:9px arial black,\"sans-serif\",sans")
]
-- Tests for the removal of outline initial values. Almost identical to those
-- shorthands in the anyOrderShorthandTests, except for the invert color.
outlineTestsInfo :: [(String, Text, Text)]
outlineTestsInfo =
[("Removes default outline-color",
"outline:invert solid 1px", "outline:solid 1px")
,("Removes default outline-style",
"outline:#fff none 1px", "outline:#fff 1px")
,("Removes default outline-width",
"outline:#fff solid medium", "outline:#fff solid")
,("Leaves none as the shortest initial value",
"outline:invert", "outline:none")
]
-- Tests for shorthands whose initial values are "medium none currentColor",
-- and can go on any order.
anyOrderShorthandTests :: Spec
anyOrderShorthandTests =
describe "Shorthands that accept values in any order (mostly: medium none currentColor)" $
traverse_ (mapM_ (matchSpec (minify <$> declaration))) d
where d = fmap f shorthandsToTest
f z = fmap (\(x,y) -> (z <> ":" <> x, z <> ":" <> y)) shorthandEquivalences
shorthandsToTest = ["border"
,"border-top"
,"border-right"
,"border-bottom"
,"border-left"
,"column-rule"]
shorthandEquivalences = [("initial", "none")
,("unset", "none")
,("none", "none")
,("medium", "none")
,("currentColor", "none")
,("medium none", "none")
,("medium currentColor", "none")
,("none currentColor", "none")
,("medium none currentColor", "none")
,("medium none red", "red")
,("thick none currentColor", "thick")
,("thick dotted currentColor", "thick dotted")]
minifyDecTestsInfo :: [(Text, Text)]
minifyDecTestsInfo =
[("BORDER-bottom: initial", "border-bottom:none")
,("BORDER-bottom: unset", "border-bottom:none")
,("BORDER-bottom: none", "border-bottom:none")
,("BORDER-bottom: medium", "border-bottom:none")
,("BORDER-bottom: currentColor", "border-bottom:none")
,("BORDER-bottom: medium none", "border-bottom:none")
,("BORDER-bottom: medium currentColor", "border-bottom:none")
,("BORDER-bottom: none currentColor", "border-bottom:none")
,("BORDER-bottom: medium none currentColor", "border-bottom:none")
,("BORDER-bottom: medium none red", "border-bottom:red")
,("BORDER-bottom: thick none currentColor", "border-bottom:thick")
,("BORDER-bottom: thick dotted currentColor", "border-bottom:thick dotted")
,("BORDER-bottom: 12px red", "border-bottom:9pt red")
-- widows inherits, and its initial value is 2
,("widows: unset", "widows:unset")
,("widows: inherit", "widows:unset")
,("widows: initial", "widows:2")
-- font-synthesis inherits, and its initial value is "weight style",
-- which are two keywords, both needed, in any order.
,("font-synthesis: unset", "font-synthesis:unset")
,("font-synthesis: inherit", "font-synthesis:unset")
,("font-synthesis: initial", "font-synthesis:initial")
,("Font-synthesis: weight style", "font-synthesis:initial")
,("font-synthesis: style weight", "font-synthesis:initial")
,("font-synthesis: weight", "font-synthesis:weight")
,("font-synthesis: style", "font-synthesis:style")
-- background-size: The first value gives the width of the corresponding image,
-- the second value its height. Its initial value is 'auto', and it doesn't
-- inherit. If only one value is given the second is assumed to be ‘auto’.
,("background-size: 96px", "background-size:6pc")
,("background-size: auto", "background-size:auto")
,("background-size: unset", "background-size:auto")
,("background-size: initial", "background-size:auto")
,("background-size: auto auto", "background-size:auto")
,("background-size: auto 96px", "background-size:auto 6pc")
,("background-size: 96px auto", "background-size:6pc")
]
spec :: Spec
spec = do cleaningTests
minifyDecTests
shorthandInitialValuesTests
declarationTests
propertySpecificTests
main :: IO ()
main = hspec spec