|
|
|
@ -217,68 +217,6 @@ it merges those items into the list (i.e., does not leave them as a sublist).
|
|
|
|
|
`(,tag ,attrs ,@elements))
|
|
|
|
|
'(div ((class "big")) "text"))
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
|
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) ;; 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).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -313,10 +251,7 @@ Is equivalent to:
|
|
|
|
|
(define (buy-book-link . text-args)
|
|
|
|
|
(apply link buy-url text-args))
|
|
|
|
|
|
|
|
|
|
(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")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
|
`buylink`: creates a link styled with the "buylink" class.
|
|
|
|
@ -328,17 +263,13 @@ whatever URL we get from the Pollen source. So we add a `url` argument.
|
|
|
|
|
(define (buylink url . text-args)
|
|
|
|
|
(apply link url #:class "buylink" text-args))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊buylink["http://foo.com"]{link text}
|
|
|
|
|
'(a ((href "http://foo.com")(class "buylink")) "link text")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(define (home-link url . text-args)
|
|
|
|
|
(apply link url #:class "home-link" text-args))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊home-link["http://foo.com"]{link text}
|
|
|
|
|
'(a ((href "http://foo.com")(class "home-link")) "link text")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
|
BTW we could also be let the rest argument capture the URL,
|
|
|
|
@ -373,13 +304,7 @@ raise an error, letting us know that we're misusing it.
|
|
|
|
|
img-tag))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(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")))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -394,8 +319,7 @@ raise an error, letting us know that we're misusing it.
|
|
|
|
|
(define base (make-txexpr 'div null text-args))
|
|
|
|
|
(attr-set base 'style (format "width: ~a" factor)))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊div-scale[.5]{Hello} '(div ((style "width: 0.5")) "Hello")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -409,9 +333,7 @@ raise an error, letting us know that we're misusing it.
|
|
|
|
|
(define base (make-txexpr 'span null text-args))
|
|
|
|
|
(attr-set base 'style (format "font-size: ~aem" ratio)))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊font-scale[.75]{Hello}
|
|
|
|
|
'(span ((style "font-size: 0.75em")) "Hello")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -424,9 +346,7 @@ raise an error, letting us know that we're misusing it.
|
|
|
|
|
(define (home-image image-path)
|
|
|
|
|
(attr-set (image image-path) 'class "home-image"))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊home-image["pic.gif"]
|
|
|
|
|
'(img ((style "width: 100%") (class "home-image") (src "images/pic.gif")))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -443,10 +363,7 @@ It also makes it possible to change the fiddly HTML markup from one central loca
|
|
|
|
|
`(div ((class "home-overlay")(style ,(format "background-image: url('~a')" img-path)))
|
|
|
|
|
(div ((class "home-overlay-inner")) ,@text-args)))
|
|
|
|
|
|
|
|
|
|
(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"))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -460,11 +377,7 @@ Any keywords passed in will be propagated to every use of the tag function.
|
|
|
|
|
|#
|
|
|
|
|
(define glyph (make-default-tag-function 'span #:class "glyph"))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊glyph{X}
|
|
|
|
|
'(span ((class "glyph")) "X"))
|
|
|
|
|
(check-txexprs-equal? ◊glyph[#:id "top"]{X}
|
|
|
|
|
'(span ((class "glyph")(id "top")) "X")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -477,12 +390,7 @@ Any keywords passed in will be propagated to every use of the tag function.
|
|
|
|
|
(define (image-wrapped img-path)
|
|
|
|
|
(attr-set* (image img-path) 'class "icon" 'style "width: 120px;" 'align "left"))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊image-wrapped{my-path}
|
|
|
|
|
'(img ((class "icon")
|
|
|
|
|
(style "width: 120px;")
|
|
|
|
|
(align "left")
|
|
|
|
|
(src "images/my-path")))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
|
|
|
|
|
@ -530,17 +438,7 @@ For simplicity, I'm not using them here.
|
|
|
|
|
(define li-tag (make-default-tag-function 'li))
|
|
|
|
|
(map (λ(lip) (apply li-tag lip)) list-of-li-paragraphs))
|
|
|
|
|
|
|
|
|
|
(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")))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -578,9 +476,7 @@ Now we can define `bullet-list` and `numbered-list` using our helper function.
|
|
|
|
|
(define bullet-list (make-list-function 'ul))
|
|
|
|
|
(define numbered-list (make-list-function 'ol))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊bullet-list{foo} '(ul (li (p "foo"))))
|
|
|
|
|
(check-txexprs-equal? ◊numbered-list{foo} '(ol (li (p "foo")))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -601,16 +497,8 @@ The `btw` tag expands to an HTML list, which we will then crack open and add a h
|
|
|
|
|
'(div ((id "btw-title")) "by the way")
|
|
|
|
|
(get-elements btw-list)))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊btw{foo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bar}
|
|
|
|
|
'(ul ((class "btw"))
|
|
|
|
|
(div ((id "btw-title")) "by the way")
|
|
|
|
|
(li (p "foo"))
|
|
|
|
|
(li (p "bar")))))
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
|
`xref`: create a styled cross-reference link, with optional destination argument.
|
|
|
|
|
|
|
|
|
@ -639,12 +527,7 @@ But to be safe, we'll raise an arity error if we get too many arguments.
|
|
|
|
|
[more-than-two-args (apply raise-arity-error 'xref (list 1 2) more-than-two-args)]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-txexprs-equal? ◊xref{target}
|
|
|
|
|
`(a ((class "xref") (href "target.html") ,no-hyphens-attr) "target"))
|
|
|
|
|
(check-txexprs-equal? ◊xref["url"]{target}
|
|
|
|
|
`(a ((class "xref") (href "url") ,no-hyphens-attr) "target"))
|
|
|
|
|
(check-exn exn:fail:contract:arity? (λ _ (xref "url" "target" "spurious-third-argument"))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
@ -670,13 +553,7 @@ The name of the source file for a page is determined by its title.
|
|
|
|
|
[xn (string-replace xn " " "-")]) ; replace word space with hyphen
|
|
|
|
|
(format "~a.html" xn)))
|
|
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
|
(check-equal? (target->url "foo?") "foo.html")
|
|
|
|
|
(check-equal? (target->url "FOO") "foo.html")
|
|
|
|
|
(check-equal? (target->url "foé") "foe.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 "Nonbreaking Space and Spaces") "nonbreaking-space-and-spaces.html"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
|
`xref-font`: special version of `xref` for the fontrec directory
|
|
|
|
@ -715,17 +592,7 @@ with arguments that will be filled in when you invoke the macro.
|
|
|
|
|
(define-heading section 'h2)
|
|
|
|
|
(define-heading chapter 'h1)
|
|
|
|
|
|
|
|
|
|
(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")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#|
|
|
|
|
|
`define-heading-from-metas`: macro for defining a function that makes a heading
|
|
|
|
|