|
|
|
#lang pollen/mode racket/base
|
|
|
|
|
|
|
|
#|
|
|
|
|
UNIT TESTS
|
|
|
|
|
|
|
|
Testing, as always, is optional, but strongly recommended. Unit tests are little one-line tests that
|
|
|
|
prove your function does what it says. As you refactor and reorganize your code, your unit tests will
|
|
|
|
let you know if you broke anything.
|
|
|
|
|
|
|
|
You can make unit tests with the `rackunit` library. Though you can put your unit tests in a separate
|
|
|
|
source file, I generally prefer to put them close to the function that they're testing. (For details
|
|
|
|
on the testing functions used below, see the docs for `rackunit`)
|
|
|
|
|
|
|
|
The ideal way to do this is with a `test` submodule. The code in a `test` submodule will only be used
|
|
|
|
a) when you run the file in DrRacket or
|
|
|
|
b) when `raco test` runs the file.
|
|
|
|
Otherwise, it is ignored.
|
|
|
|
|
|
|
|
We'll use the `module+` syntax for this. As the name suggests, `module+` creates a submodule that
|
|
|
|
incorporates everything else already in the source file. Moreover, all of our `module+ test` blocks
|
|
|
|
will be combined into a single submodule.
|
|
|
|
|#
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(require rackunit txexpr "pollen.rkt") ;; always include this at the start of the test submodule
|
|
|
|
|
|
|
|
;; We use `check-txexprs-equal?` rather than `check-equal?` because it's a little more lenient:
|
|
|
|
;; it allows the attributes of two txexprs to be in a different order,
|
|
|
|
;; yet still be considered equal (because ordering of attributes is not semantically significant).
|
|
|
|
(check-txexprs-equal? (link "http://foo.com" "link text")
|
|
|
|
'(a ((href "http://foo.com")) "link text"))
|
|
|
|
|
|
|
|
;; The last test was fine, but it can be even better if we use a Pollen-mode command on the left.
|
|
|
|
;; That way, we can directly compare the command as it appears in Pollen input
|
|
|
|
;; with how it appears in the output.
|
|
|
|
(check-txexprs-equal? ◊link["http://foo.com"]{link text}
|
|
|
|
'(a ((href "http://foo.com")) "link text"))
|
|
|
|
|
|
|
|
;; It's wise to test as many valid input situations as you can.
|
|
|
|
(check-txexprs-equal? ◊link["http://foo.com" #:class 'main]{link text}
|
|
|
|
'(a ((href "http://foo.com")(class "main")) "link text"))
|
|
|
|
(check-txexprs-equal? ◊link["http://foo.com"]
|
|
|
|
'(a ((href "http://foo.com")) "http://foo.com"))
|
|
|
|
|
|
|
|
;; Strictly speaking, you could also write the last Pollen command like so:
|
|
|
|
(check-txexprs-equal? ◊link{http://foo.com} '(a ((href "http://foo.com")) "http://foo.com"))
|
|
|
|
|
|
|
|
;; That's not wrong. But in the interests of code readability,
|
|
|
|
;; I like to reserve the curly brackets in a Pollen command
|
|
|
|
;; for material that I expect to see displayed in the output
|
|
|
|
;; (e.g., textual and other content),
|
|
|
|
;; and use the square brackets for the other arguments.
|
|
|
|
|
|
|
|
;; You can also check that errors arise when they should.
|
|
|
|
;; Note that when testing for exceptions, you need to wrap your test expression in a function
|
|
|
|
;; (so that its evaluation can be delayed, otherwise you'd get the error immediately.)
|
|
|
|
;; The `(λ _ expression)` notation is a simple way.
|
|
|
|
;; (The `_` is the idiomatic way to notate something that will be ignored, in this case arguments.)
|
|
|
|
(check-exn exn:fail? (λ _ ◊link[])) ; no arguments
|
|
|
|
(check-exn exn:fail? (λ _ ◊link[#:invalid-keyword 42])) ; invalid keyword argument
|
|
|
|
(check-exn exn:fail? (λ _ ◊link[#f]))) ; invalid argument
|
|
|
|
|
|
|
|
;; For the sake of brevity, I'm going to write just one test for the remaining functions.
|
|
|
|
;; But you're encouraged to add more tests (or break the existing ones and see what happens).
|
|
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
;; notice that we use `buy-url` in our test result.
|
|
|
|
;; That way, if we change the value of `buy-url`, the test won't break.
|
|
|
|
(check-txexprs-equal? ◊buy-book-link{link text} `(a ((href ,buy-url)) "link text")))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊buylink["http://foo.com"]{link text}
|
|
|
|
'(a ((href "http://foo.com")(class "buylink")) "link text")))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊home-link["http://foo.com"]{link text}
|
|
|
|
'(a ((href "http://foo.com")(class "home-link")) "link text")))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊image["pic.gif"]
|
|
|
|
'(img ((style "width: 100%") (class "bordered")(src "images/pic.gif"))))
|
|
|
|
(check-txexprs-equal? ◊image[#:border #f "pic.gif"]
|
|
|
|
'(img ((style "width: 100%")(src "images/pic.gif"))))
|
|
|
|
(check-txexprs-equal? ◊image[#:width "50%" "pic.gif"]
|
|
|
|
'(img ((style "width: 50%")(class "bordered")(src "images/pic.gif")))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊div-scale[.5]{Hello} '(div ((style "width: 0.5")) "Hello")))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊font-scale[.75]{Hello}
|
|
|
|
'(span ((style "font-size: 0.75em")) "Hello")))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊home-image["pic.gif"]
|
|
|
|
'(img ((style "width: 100%") (class "home-image") (src "images/pic.gif")))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊home-overlay["pic.gif"]{Hello}
|
|
|
|
'(div ((class "home-overlay") (style "background-image: url('pic.gif')"))
|
|
|
|
(div ((class "home-overlay-inner")) "Hello"))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊glyph{X}
|
|
|
|
'(span ((class "glyph")) "X"))
|
|
|
|
(check-txexprs-equal? ◊glyph[#:id "top"]{X}
|
|
|
|
'(span ((class "glyph")(id "top")) "X")))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊image-wrapped{my-path}
|
|
|
|
'(img ((class "icon")
|
|
|
|
(style "width: 120px;")
|
|
|
|
(align "left")
|
|
|
|
(src "images/my-path")))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-equal? (detect-list-items '("foo" "\n" "bar")) ; linebreak, not list item break
|
|
|
|
'((li (p "foo" (br) "bar"))))
|
|
|
|
(check-equal? (detect-list-items '("foo" "\n" "\n" "bar")) ; paragraph break, not list item break
|
|
|
|
'((li (p "foo") (p "bar"))))
|
|
|
|
(check-equal? (detect-list-items '("foo" "\n" "\n" "\n" "bar")) ; list item break
|
|
|
|
'((li (p "foo")) (li (p "bar"))))
|
|
|
|
(check-equal? (detect-list-items '("foo" "\n\n\n" "bar")) ; list item break, concatenated
|
|
|
|
'((li (p "foo")) (li (p "bar"))))
|
|
|
|
(check-equal? (detect-list-items '("foo" "\n" "\n" "\n\n\n" "bar")) ; list item break
|
|
|
|
'((li (p "foo")) (li (p "bar")))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊bullet-list{foo} '(ul (li (p "foo"))))
|
|
|
|
(check-txexprs-equal? ◊numbered-list{foo} '(ol (li (p "foo")))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊btw{foo
|
|
|
|
|
|
|
|
|
|
|
|
bar}
|
|
|
|
'(ul ((class "btw"))
|
|
|
|
(div ((id "btw-title")) "by the way")
|
|
|
|
(li (p "foo"))
|
|
|
|
(li (p "bar")))))
|
|
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊xref{word space}
|
|
|
|
`(a ((class "xref") (href "word-spaces.html") ,no-hyphens-attr) "word space"))
|
|
|
|
(check-txexprs-equal? ◊xref["word-spaces.html"]{target}
|
|
|
|
`(a ((class "xref") (href "word-spaces.html") ,no-hyphens-attr) "target"))
|
|
|
|
(check-exn exn:fail:contract:arity? (λ _ (xref "word-spaces.html" "target" "spurious-third-argument"))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-equal? (target->url "word space") "word-spaces.html")
|
|
|
|
(check-equal? (target->url "WORD SPACE") "word-spaces.html")
|
|
|
|
(check-equal? (target->url "word spacé") "word-spaces.html")
|
|
|
|
(check-equal? (target->url "Foreword Lengthy Title") "foreword.html")
|
|
|
|
(check-equal? (target->url "Table of Contents and Other Nonsense") "toc.html")
|
|
|
|
(check-equal? (target->url "Word Spaces") "word-spaces.html"))
|
|
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊topic{foo}
|
|
|
|
'(h3 ((class "topic")) "foo"))
|
|
|
|
(check-txexprs-equal? ◊subhead{foo}
|
|
|
|
'(h3 ((class "subhead")) "foo"))
|
|
|
|
(check-txexprs-equal? ◊font-headline{foo}
|
|
|
|
'(h3 ((class "font-headline")) "foo"))
|
|
|
|
(check-txexprs-equal? ◊section{foo}
|
|
|
|
'(h2 ((class "section")) "foo"))
|
|
|
|
(check-txexprs-equal? ◊chapter{foo}
|
|
|
|
'(h1 ((class "chapter")) "foo")))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(let ([my-fake-metas (hash 'title "Fake Title" 'white "noise")])
|
|
|
|
(check-txexprs-equal? ◊topic-from-metas[my-fake-metas]
|
|
|
|
'(h3 ((class "topic")) "Fake Title"))
|
|
|
|
(check-txexprs-equal? ◊section-from-metas[my-fake-metas]
|
|
|
|
'(h2 ((class "section")) "Fake Title"))
|
|
|
|
(check-txexprs-equal? ◊chapter-from-metas[my-fake-metas]
|
|
|
|
'(h1 ((class "chapter")) "Fake Title"))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? ◊hanging-topic["Topic name"]{One-line explanation}
|
|
|
|
`(div ((class "hanging-topic") ,no-hyphens-attr) "Topic name"
|
|
|
|
(p (,no-hyphens-attr) "One-line explanation"))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal?
|
|
|
|
◊(quick-table "heading-one | heading-two" "\n"
|
|
|
|
" three | four" "\n"
|
|
|
|
"five | six ")
|
|
|
|
'(table (tr (th "heading-one") (th "heading-two"))
|
|
|
|
(tr (td "three") (td "four"))
|
|
|
|
(tr (td "five") (td "six")))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? (hyphenate-block `(div "snowman" (span (,no-hyphens-attr) "snowman")))
|
|
|
|
`(div "snow\u00ADman" (span (,no-hyphens-attr) "snowman"))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-txexprs-equal? (make-quotes-hangable "“Who is it?”")
|
|
|
|
'(quo "" (dquo-push) (dquo-pull "“") "Who is it?”")))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-equal? (fix-em-dashes "Hey — you!") "Hey—you!")
|
|
|
|
(check-equal? (fix-em-dashes "Hey—you!") "Hey—you!"))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
(check-equal? (capitalize-first-letter "foo dog") "Foo dog"))
|