content: {-# 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.Declaration
  
  declarationTests :: Spec
  declarationTests =
      describe "declaration" $
        mapM_ (matchSpec declaration) declarationTestsInfo
  
  propertySpecificTests :: Spec
  propertySpecificTests =
      describe "property specific changes" $
        mapM_ (matchSpec f) propertySpecificTestsInfo
    where f = minifyWithTestConfig <$> 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")
  
    ,("overflow:unset",           "overflow:unset")
    ,("overflow:initial",         "overflow:unset")
    ,("overflow:visible",         "overflow:unset")
    ,("overflow:visible visible", "overflow:unset")
    ,("overflow:clip visible",    "overflow:clip visible")
    ,("overflow:visible clip",    "overflow:visible clip")
    ,("overflow:clip clip",       "overflow:clip")
  
    ,("overscroll-behavior:unset",           "overscroll-behavior:auto")
    ,("overscroll-behavior:initial",         "overscroll-behavior:auto")
    ,("overscroll-behavior:auto",            "overscroll-behavior:auto")
    ,("overscroll-behavior:auto auto",       "overscroll-behavior:auto")
    ,("overscroll-behavior:none auto",       "overscroll-behavior:none auto")
    ,("overscroll-behavior:auto none",       "overscroll-behavior:auto none")
    ,("overscroll-behavior:contain contain", "overscroll-behavior:contain")
    ,("overscroll-behavior:none none",       "overscroll-behavior:none")
  
    ,("border-bottom-left-radius:40% 40%",  "border-bottom-left-radius:40%")
    ,("border-bottom-right-radius:0 0",     "border-bottom-right-radius:0")
    ,("border-top-left-radius:initial",     "border-top-left-radius:0")
    ,("border-top-right-radius:5px 6px",    "border-top-right-radius:5px 6px")
  
    ,("text-emphasis-position:right over",  "text-emphasis-position:unset")
    ,("text-emphasis-position:over right",  "text-emphasis-position:unset")
    ,("text-emphasis-position:initial",     "text-emphasis-position:unset")
    ,("text-emphasis-position:over left",   "text-emphasis-position:over left")
    ,("text-emphasis-position:right under", "text-emphasis-position:right under")
    ]
  
  
  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 (minifyWithTestConfig <$> declaration)) minifyDecTestsInfo
  
  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 :: String -> [(String, Text, Text)] -> SpecWith ()
          testMatches desc i = describe desc $
                            mapM_ (matchSpecWithDesc (minifyWithTestConfig <$> 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 (minifyWithTestConfig <$> 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
  