hamlet-0.10.5: test/HamletTest.hs
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
module HamletTest (specs) where
import Test.HUnit hiding (Test)
import Test.Hspec
import Test.Hspec.HUnit
import Prelude hiding (reverse)
import Text.Hamlet
import Data.List (intercalate)
import qualified Data.Text.Lazy as T
import qualified Data.List
import qualified Data.List as L
import qualified Data.Map as Map
import Data.Text (Text, pack, unpack)
import Data.Monoid (mappend)
import qualified Data.Set as Set
import qualified Text.Blaze.Renderer.Text
import Text.Blaze (toHtml, preEscapedString)
specs = describe "hamlet"
[ it "empty" caseEmpty
, it "static" caseStatic
, it "tag" caseTag
, it "var" caseVar
, it "var chain " caseVarChain
, it "url" caseUrl
, it "url chain " caseUrlChain
, it "embed" caseEmbed
, it "embed chain " caseEmbedChain
, it "if" caseIf
, it "if chain " caseIfChain
, it "else" caseElse
, it "else chain " caseElseChain
, it "elseif" caseElseIf
, it "elseif chain " caseElseIfChain
, it "list" caseList
, it "list chain" caseListChain
, it "with" caseWith
, it "with multi" caseWithMulti
, it "with chain" caseWithChain
, it "with comma string" caseWithCommaString
, it "with multi scope" caseWithMultiBindingScope
, it "script not empty" caseScriptNotEmpty
, it "meta empty" caseMetaEmpty
, it "input empty" caseInputEmpty
, it "multiple classes" caseMultiClass
, it "attrib order" caseAttribOrder
, it "nothing" caseNothing
, it "nothing chain " caseNothingChain
, it "just" caseJust
, it "just chain " caseJustChain
, it "constructor" caseConstructor
, it "url + params" caseUrlParams
, it "escape" caseEscape
, it "empty statement list" caseEmptyStatementList
, it "attribute conditionals" caseAttribCond
, it "non-ascii" caseNonAscii
, it "maybe function" caseMaybeFunction
, it "trailing dollar sign" caseTrailingDollarSign
, it "non leading percent sign" caseNonLeadingPercent
, it "quoted attributes" caseQuotedAttribs
, it "spaced derefs" caseSpacedDerefs
, it "attrib vars" caseAttribVars
, it "strings and html" caseStringsAndHtml
, it "nesting" caseNesting
, it "trailing space" caseTrailingSpace
, it "currency symbols" caseCurrency
, it "external" caseExternal
, it "parens" caseParens
, it "hamlet literals" caseHamletLiterals
, it "hamlet' and xhamlet'" caseHamlet'
, it "comments" $ do
-- FIXME reconsider Hamlet comment syntax?
helper "" [hamlet|$# this is a comment
$# another comment
$#a third one|]
, it "ignores a blank line" $ do
helper "<p>foo</p>" [hamlet|
<p
foo
|]
, it "hamlet angle bracket syntax" $
helper "<p class=\"foo\" height=\"100\"><span id=\"bar\" width=\"50\">HELLO</span></p>"
[hamlet|
<p.foo height="100"
<span #bar width=50>HELLO
|]
, it "hamlet module names" $
let foo = "foo" in
helper "oof oof 3.14 -5"
[hamlet|#{Data.List.reverse foo} #
#{L.reverse foo} #
#{show 3.14} #{show -5}|]
, it "single dollar at and caret" $ do
helper "$@^" [hamlet|$@^|]
helper "#{@{^{" [hamlet|#\{@\{^\{|]
, it "dollar operator" $ do
let val = (1, (2, 3))
helper "2" [hamlet|#{ show $ fst $ snd val }|]
helper "2" [hamlet|#{ show $ fst $ snd $ val}|]
, it "in a row" $ do
helper "1" [hamlet|#{ show $ const 1 2 }|]
, it "embedded slash" $ do
helper "///" [hamlet|///|]
, it "string literals" $ do
helper "string" [hamlet|#{"string"}|]
helper "string" [hamlet|#{id "string"}|]
helper "gnirts" [hamlet|#{L.reverse $ id "string"}|]
helper "str"ing" [hamlet|#{"str\"ing"}|]
helper "str<ing" [hamlet|#{"str<ing"}|]
, it "interpolated operators" $ do
helper "3" [hamlet|#{show $ (+) 1 2}|]
helper "6" [hamlet|#{show $ sum $ (:) 1 ((:) 2 $ return 3)}|]
, it "HTML comments" $ do
helper "<p>1</p><p>2 not ignored</p>" [hamlet|
<p>1
<!-- ignored comment -->
<p
2
<!-- ignored --> not ignored<!-- ignored -->
|]
, it "nested maybes" $ do
let muser = Just "User" :: Maybe String
mprof = Nothing :: Maybe Int
m3 = Nothing :: Maybe String
helper "justnothing" [hamlet|
$maybe user <- muser
$maybe profile <- mprof
First two are Just
$maybe desc <- m3
\ and left us a description:
<p>#{desc}
$nothing
and has left us no description.
$nothing
justnothing
$nothing
<h1>No such Person exists.
|]
, it "conditional class" $ do
helper "<p class=\"current\"></p>"
[hamlet|<p :False:.ignored :True:.current|]
helper "<p class=\"1 3 2 4\"></p>"
[hamlet|<p :True:.1 :True:class=2 :False:.a :False:class=b .3 class=4|]
helper "<p class=\"foo bar baz\"></p>"
[hamlet|<p class=foo class=bar class=baz|]
, it "forall on Foldable" $ do
let set = Set.fromList [1..5 :: Int]
helper "12345" [hamlet|
$forall x <- set
#{x}
|]
, it "non-poly HTML" $ do
helperHtml "<h1>HELLO WORLD</h1>" [shamlet|
<h1>HELLO WORLD
|]
helperHtml "<h1>HELLO WORLD</h1>" $(shamletFile "test/hamlets/nonpolyhtml.hamlet")
, it "non-poly Hamlet" $ do
let embed = [hamlet|<p>EMBEDDED|]
helper "<h1>url</h1><p>EMBEDDED</p>" [hamlet|
<h1>@{Home}
^{embed}
|]
helper "<h1>url</h1>" $(hamletFile "test/hamlets/nonpolyhamlet.hamlet")
, it "non-poly IHamlet" $ do
let embed = [ihamlet|<p>EMBEDDED|]
ihelper "<h1>Adios</h1><p>EMBEDDED</p>" [ihamlet|
<h1>_{Goodbye}
^{embed}
|]
ihelper "<h1>Hola</h1>" $(ihamletFile "test/hamlets/nonpolyihamlet.hamlet")
, it "pattern-match tuples: forall" $ do
let people = [("Michael", 26), ("Miriam", 25)]
helper "<dl><dt>Michael</dt><dd>26</dd><dt>Miriam</dt><dd>25</dd></dl>" [hamlet|
<dl>
$forall (name, age) <- people
<dt>#{name}
<dd>#{show age}
|]
, it "pattern-match tuples: maybe" $ do
let people = Just ("Michael", 26)
helper "<dl><dt>Michael</dt><dd>26</dd></dl>" [hamlet|
<dl>
$maybe (name, age) <- people
<dt>#{name}
<dd>#{show age}
|]
, it "pattern-match tuples: with" $ do
let people = ("Michael", 26)
helper "<dl><dt>Michael</dt><dd>26</dd></dl>" [hamlet|
<dl>
$with (name, age) <- people
<dt>#{name}
<dd>#{show age}
|]
, it "list syntax for interpolation" $ do
helper "<ul><li>1</li><li>2</li><li>3</li></ul>" [hamlet|
<ul>
$forall num <- [1, 2, 3]
<li>#{show num}
|]
, it "infix operators" $
helper "5" [hamlet|#{show $ (4 + 5) - (2 + 2)}|]
, it "infix operators with parens" $
helper "5" [hamlet|#{show (2 + 3)}|]
]
data Url = Home | Sub SubUrl
data SubUrl = SubUrl
render :: Url -> [(Text, Text)] -> Text
render Home qs = pack "url" `mappend` showParams qs
render (Sub SubUrl) qs = pack "suburl" `mappend` showParams qs
showParams :: [(Text, Text)] -> Text
showParams [] = pack ""
showParams z =
pack $ '?' : intercalate "&" (map go z)
where
go (x, y) = go' x ++ '=' : go' y
go' = concatMap encodeUrlChar . unpack
-- | Taken straight from web-encodings; reimplemented here to avoid extra
-- dependencies.
encodeUrlChar :: Char -> String
encodeUrlChar c
-- List of unreserved characters per RFC 3986
-- Gleaned from http://en.wikipedia.org/wiki/Percent-encoding
| 'A' <= c && c <= 'Z' = [c]
| 'a' <= c && c <= 'z' = [c]
| '0' <= c && c <= '9' = [c]
encodeUrlChar c@'-' = [c]
encodeUrlChar c@'_' = [c]
encodeUrlChar c@'.' = [c]
encodeUrlChar c@'~' = [c]
encodeUrlChar ' ' = "+"
encodeUrlChar y =
let (a, c) = fromEnum y `divMod` 16
b = a `mod` 16
showHex' x
| x < 10 = toEnum $ x + (fromEnum '0')
| x < 16 = toEnum $ x - 10 + (fromEnum 'A')
| otherwise = error $ "Invalid argument to showHex: " ++ show x
in ['%', showHex' b, showHex' c]
data Arg url = Arg
{ getArg :: Arg url
, var :: Html
, url :: Url
, embed :: HtmlUrl url
, true :: Bool
, false :: Bool
, list :: [Arg url]
, nothing :: Maybe String
, just :: Maybe String
, urlParams :: (Url, [(Text, Text)])
}
theArg :: Arg url
theArg = Arg
{ getArg = theArg
, var = toHtml "<var>"
, url = Home
, embed = [hamlet|embed|]
, true = True
, false = False
, list = [theArg, theArg, theArg]
, nothing = Nothing
, just = Just "just"
, urlParams = (Home, [(pack "foo", pack "bar"), (pack "foo1", pack "bar1")])
}
helperHtml :: String -> Html -> Assertion
helperHtml res h = do
let x = Text.Blaze.Renderer.Text.renderHtml h
T.pack res @=? x
helper :: String -> HtmlUrl Url -> Assertion
helper res h = do
let x = Text.Blaze.Renderer.Text.renderHtml $ h render
T.pack res @=? x
caseEmpty :: Assertion
caseEmpty = helper "" [hamlet||]
caseStatic :: Assertion
caseStatic = helper "some static content" [hamlet|some static content|]
caseTag :: Assertion
caseTag = do
helper "<p class=\"foo\"><div id=\"bar\">baz</div></p>" [hamlet|
<p .foo
<#bar>baz
|]
helper "<p class=\"foo.bar\"><div id=\"bar\">baz</div></p>" [hamlet|
<p class=foo.bar
<#bar>baz
|]
caseVar :: Assertion
caseVar = do
helper "<var>" [hamlet|#{var theArg}|]
caseVarChain :: Assertion
caseVarChain = do
helper "<var>" [hamlet|#{var (getArg (getArg (getArg theArg)))}|]
caseUrl :: Assertion
caseUrl = do
helper (unpack $ render Home []) [hamlet|@{url theArg}|]
caseUrlChain :: Assertion
caseUrlChain = do
helper (unpack $ render Home []) [hamlet|@{url (getArg (getArg (getArg theArg)))}|]
caseEmbed :: Assertion
caseEmbed = do
helper "embed" [hamlet|^{embed theArg}|]
caseEmbedChain :: Assertion
caseEmbedChain = do
helper "embed" [hamlet|^{embed (getArg (getArg (getArg theArg)))}|]
caseIf :: Assertion
caseIf = do
helper "if" [hamlet|
$if true theArg
if
|]
caseIfChain :: Assertion
caseIfChain = do
helper "if" [hamlet|
$if true (getArg (getArg (getArg theArg)))
if
|]
caseElse :: Assertion
caseElse = helper "else" [hamlet|
$if false theArg
if
$else
else
|]
caseElseChain :: Assertion
caseElseChain = helper "else" [hamlet|
$if false (getArg (getArg (getArg theArg)))
if
$else
else
|]
caseElseIf :: Assertion
caseElseIf = helper "elseif" [hamlet|
$if false theArg
if
$elseif true theArg
elseif
$else
else
|]
caseElseIfChain :: Assertion
caseElseIfChain = helper "elseif" [hamlet|
$if false(getArg(getArg(getArg theArg)))
if
$elseif true(getArg(getArg(getArg theArg)))
elseif
$else
else
|]
caseList :: Assertion
caseList = do
helper "xxx" [hamlet|
$forall _x <- (list theArg)
x
|]
caseListChain :: Assertion
caseListChain = do
helper "urlurlurl" [hamlet|
$forall x <- list(getArg(getArg(getArg(getArg(getArg (theArg))))))
@{url x}
|]
caseWith :: Assertion
caseWith = do
helper "it's embedded" [hamlet|
$with n <- embed theArg
it's ^{n}ded
|]
caseWithMulti :: Assertion
caseWithMulti = do
helper "it's embedded" [hamlet|
$with n <- embed theArg, m <- true theArg
$if m
it's ^{n}ded
|]
caseWithChain :: Assertion
caseWithChain = do
helper "it's true" [hamlet|
$with n <- true(getArg(getArg(getArg(getArg theArg))))
$if n
it's true
|]
-- in multi-with binding, make sure that a comma in a string doesn't confuse the parser.
caseWithCommaString :: Assertion
caseWithCommaString = do
helper "it's , something" [hamlet|
$with n <- " , something"
it's #{n}
|]
caseWithMultiBindingScope :: Assertion
caseWithMultiBindingScope = do
helper "it's , something" [hamlet|
$with n <- " , something", y <- n
it's #{y}
|]
caseScriptNotEmpty :: Assertion
caseScriptNotEmpty = helper "<script></script>" [hamlet|<script|]
caseMetaEmpty :: Assertion
caseMetaEmpty = do
helper "<meta>" [hamlet|<meta|]
helper "<meta/>" [xhamlet|<meta|]
helper "<meta>" [hamlet|<meta>|]
helper "<meta/>" [xhamlet|<meta>|]
caseInputEmpty :: Assertion
caseInputEmpty = do
helper "<input>" [hamlet|<input|]
helper "<input/>" [xhamlet|<input|]
helper "<input>" [hamlet|<input>|]
helper "<input/>" [xhamlet|<input>|]
caseMultiClass :: Assertion
caseMultiClass = do
helper "<div class=\"foo bar\"></div>" [hamlet|<.foo.bar|]
helper "<div class=\"foo bar\"></div>" [hamlet|<.foo.bar>|]
caseAttribOrder :: Assertion
caseAttribOrder = do
helper "<meta 1 2 3>" [hamlet|<meta 1 2 3|]
helper "<meta 1 2 3>" [hamlet|<meta 1 2 3>|]
caseNothing :: Assertion
caseNothing = do
helper "" [hamlet|
$maybe _n <- nothing theArg
nothing
|]
helper "nothing" [hamlet|
$maybe _n <- nothing theArg
something
$nothing
nothing
|]
caseNothingChain :: Assertion
caseNothingChain = helper "" [hamlet|
$maybe n <- nothing(getArg(getArg(getArg theArg)))
nothing #{n}
|]
caseJust :: Assertion
caseJust = helper "it's just" [hamlet|
$maybe n <- just theArg
it's #{n}
|]
caseJustChain :: Assertion
caseJustChain = helper "it's just" [hamlet|
$maybe n <- just(getArg(getArg(getArg theArg)))
it's #{n}
|]
caseConstructor :: Assertion
caseConstructor = do
helper "url" [hamlet|@{Home}|]
helper "suburl" [hamlet|@{Sub SubUrl}|]
let text = "<raw text>"
helper "<raw text>" [hamlet|#{preEscapedString text}|]
caseUrlParams :: Assertion
caseUrlParams = do
helper "url?foo=bar&foo1=bar1" [hamlet|@?{urlParams theArg}|]
caseEscape :: Assertion
caseEscape = do
helper "#this is raw\n " [hamlet|
\#this is raw
\
\
|]
helper "$@^" [hamlet|$@^|]
caseEmptyStatementList :: Assertion
caseEmptyStatementList = do
helper "" [hamlet|$if True|]
helper "" [hamlet|$maybe _x <- Nothing|]
let emptyList = []
helper "" [hamlet|$forall _x <- emptyList|]
caseAttribCond :: Assertion
caseAttribCond = do
helper "<select></select>" [hamlet|<select :False:selected|]
helper "<select selected></select>" [hamlet|<select :True:selected|]
helper "<meta var=\"foo:bar\">" [hamlet|<meta var=foo:bar|]
helper "<select selected></select>"
[hamlet|<select :true theArg:selected|]
helper "<select></select>" [hamlet|<select :False:selected>|]
helper "<select selected></select>" [hamlet|<select :True:selected>|]
helper "<meta var=\"foo:bar\">" [hamlet|<meta var=foo:bar>|]
helper "<select selected></select>"
[hamlet|<select :true theArg:selected>|]
caseNonAscii :: Assertion
caseNonAscii = do
helper "עִבְרִי" [hamlet|עִבְרִי|]
caseMaybeFunction :: Assertion
caseMaybeFunction = do
helper "url?foo=bar&foo1=bar1" [hamlet|
$maybe x <- Just urlParams
@?{x theArg}
|]
caseTrailingDollarSign :: Assertion
caseTrailingDollarSign =
helper "trailing space \ndollar sign #" [hamlet|trailing space #
\
dollar sign #\
|]
caseNonLeadingPercent :: Assertion
caseNonLeadingPercent =
helper "<span style=\"height:100%\">foo</span>" [hamlet|
<span style=height:100%>foo
|]
caseQuotedAttribs :: Assertion
caseQuotedAttribs =
helper "<input type=\"submit\" value=\"Submit response\">" [hamlet|
<input type=submit value="Submit response"
|]
caseSpacedDerefs :: Assertion
caseSpacedDerefs = do
helper "<var>" [hamlet|#{var theArg}|]
helper "<div class=\"<var>\"></div>" [hamlet|<.#{var theArg}|]
caseAttribVars :: Assertion
caseAttribVars = do
helper "<div id=\"<var>\"></div>" [hamlet|<##{var theArg}|]
helper "<div class=\"<var>\"></div>" [hamlet|<.#{var theArg}|]
helper "<div f=\"<var>\"></div>" [hamlet|< f=#{var theArg}|]
helper "<div id=\"<var>\"></div>" [hamlet|<##{var theArg}>|]
helper "<div class=\"<var>\"></div>" [hamlet|<.#{var theArg}>|]
helper "<div f=\"<var>\"></div>" [hamlet|< f=#{var theArg}>|]
caseStringsAndHtml :: Assertion
caseStringsAndHtml = do
let str = "<string>"
let html = preEscapedString "<html>"
helper "<string> <html>" [hamlet|#{str} #{html}|]
caseNesting :: Assertion
caseNesting = do
helper
"<table><tbody><tr><td>1</td></tr><tr><td>2</td></tr></tbody></table>"
[hamlet|
<table
<tbody
$forall user <- users
<tr
<td>#{user}
|]
helper
(concat
[ "<select id=\"foo\" name=\"foo\"><option selected></option>"
, "<option value=\"true\">Yes</option>"
, "<option value=\"false\">No</option>"
, "</select>"
])
[hamlet|
<select #"#{name}" name=#{name}
<option :isBoolBlank val:selected
<option value=true :isBoolTrue val:selected>Yes
<option value=false :isBoolFalse val:selected>No
|]
where
users = ["1", "2"]
name = "foo"
val = 5 :: Int
isBoolBlank _ = True
isBoolTrue _ = False
isBoolFalse _ = False
caseTrailingSpace :: Assertion
caseTrailingSpace =
helper "" [hamlet| |]
caseCurrency :: Assertion
caseCurrency =
helper foo [hamlet|#{foo}|]
where
foo = "eg: 5, $6, €7.01, £75"
caseExternal :: Assertion
caseExternal = do
helper "foo<br>" $(hamletFile "test/hamlets/external.hamlet")
helper "foo<br/>" $(xhamletFile "test/hamlets/external.hamlet")
where
foo = "foo"
caseParens :: Assertion
caseParens = do
let plus = (++)
x = "x"
y = "y"
helper "xy" [hamlet|#{(plus x) y}|]
helper "xxy" [hamlet|#{plus (plus x x) y}|]
let alist = ["1", "2", "3"]
helper "123" [hamlet|
$forall x <- (id id id id alist)
#{x}
|]
caseHamletLiterals :: Assertion
caseHamletLiterals = do
helper "123" [hamlet|#{show 123}|]
helper "123.456" [hamlet|#{show 123.456}|]
helper "-123" [hamlet|#{show -123}|]
helper "-123.456" [hamlet|#{show -123.456}|]
helper' :: String -> Html -> Assertion
helper' res h = T.pack res @=? Text.Blaze.Renderer.Text.renderHtml h
caseHamlet' :: Assertion
caseHamlet' = do
helper' "foo" [shamlet|foo|]
helper' "foo" [xshamlet|foo|]
helper "<br>" $ const $ [shamlet|<br|]
helper "<br/>" $ const $ [xshamlet|<br|]
-- new with generalized stuff
helper' "foo" [shamlet|foo|]
helper' "foo" [xshamlet|foo|]
helper "<br>" $ const $ [shamlet|<br|]
helper "<br/>" $ const $ [xshamlet|<br|]
instance Show Url where
show _ = "FIXME remove this instance show Url"
caseDiffBindNames :: Assertion
caseDiffBindNames = do
let list = words "1 2 3"
-- FIXME helper "123123" $(hamletFileDebug "test/hamlets/external-debug3.hamlet")
error "test has been disabled"
caseTrailingSpaces :: Assertion
caseTrailingSpaces = helper "" [hamlet|
$if True
$elseif False
$else
$maybe x <- Nothing
$nothing
$forall x <- empty
|]
where
empty = []
data Msg = Hello | Goodbye
ihelper :: String -> HtmlUrlI18n Msg Url -> Assertion
ihelper res h = do
let x = Text.Blaze.Renderer.Text.renderHtml $ h showMsg render
T.pack res @=? x
where
showMsg Hello = preEscapedString "Hola"
showMsg Goodbye = preEscapedString "Adios"