431 lines
17 KiB
Clojure
431 lines
17 KiB
Clojure
(ns clj-jtwig.functions-test
|
|
(:require [clojure.test :refer :all]
|
|
[clj-jtwig.core :refer :all]
|
|
[clj-jtwig.functions :refer :all]))
|
|
|
|
; TODO: is there a better way to test that something is an instance of some object generated by reify?
|
|
(defn valid-function-handler? [x]
|
|
(and (not (nil? x))
|
|
(-> x
|
|
(class)
|
|
(.getName)
|
|
(.startsWith "clj_jtwig.functions$make_function_handler"))))
|
|
|
|
(deftest template-functions
|
|
(testing "Adding custom template functions"
|
|
(do
|
|
(reset-functions!)
|
|
|
|
(is (valid-function-handler?
|
|
(deftwigfn "add" [a b]
|
|
(+ a b))))
|
|
|
|
(is (true? (function-exists? "add")))
|
|
(is (false? (function-exists? "foobar")))
|
|
|
|
(is (valid-function-handler?
|
|
(deftwigfn "add" [a b]
|
|
(+ a b))))
|
|
|
|
(is (= (render "{{add(1, 2)}}" nil)
|
|
"3")
|
|
"calling a custom function")
|
|
(is (= (render "{{add(a, b)}}" {:a 1 :b 2})
|
|
"3")
|
|
"calling a custom function, passing in variables from the model-map as arguments")
|
|
(is (= (render "{{x|add(1)}}" {:x 1})
|
|
"2")
|
|
"calling a custom function using the 'filter' syntax")
|
|
|
|
(reset-functions!)))
|
|
|
|
(testing "Custom template function aliases"
|
|
(do
|
|
(reset-functions!)
|
|
|
|
(is (valid-function-handler?
|
|
(defaliasedtwigfn "add" [a b]
|
|
["plus" "myAddFn"]
|
|
(+ a b))))
|
|
|
|
(is (true? (function-exists? "add")))
|
|
(is (true? (function-exists? "plus")))
|
|
(is (true? (function-exists? "myAddFn")))
|
|
|
|
(is (= (render "{{add(1, 2)}}" nil)
|
|
"3")
|
|
"calling a custom function by name")
|
|
(is (= (render "{{plus(1, 2)}}" nil)
|
|
"3")
|
|
"calling a custom function by alias")
|
|
(is (= (render "{{myAddFn(1, 2)}}" nil)
|
|
"3")
|
|
"calling a custom function by another alias")
|
|
|
|
(reset-functions!)))
|
|
|
|
(testing "Fixed and variable number of template function arguments"
|
|
(do
|
|
(reset-functions!)
|
|
|
|
(is (valid-function-handler?
|
|
(deftwigfn "add2" [a b]
|
|
(+ a b))))
|
|
(is (true? (function-exists? "add2")))
|
|
(is (valid-function-handler?
|
|
(deftwigfn "addAll" [& numbers]
|
|
(apply + numbers))))
|
|
(is (true? (function-exists? "addAll")))
|
|
|
|
(is (= (render "{{add2(1, 2)}}" nil)
|
|
"3")
|
|
"fixed number of arguments (correct amount)")
|
|
(is (thrown-with-msg?
|
|
Exception
|
|
#"clojure\.lang\.ArityException: Wrong number of args"
|
|
(render "{{add2(1)}}" nil)))
|
|
(is (= (render "{{addAll(1, 2, 3, 4, 5)}}" nil)
|
|
"15")
|
|
"variable number of arguments (non-zero)")
|
|
(is (= (render "{{addAll}}" nil)
|
|
"null")
|
|
"variable number of arguments (zero)")
|
|
|
|
(reset-functions!)))
|
|
|
|
(testing "Passing different data structures to template functions"
|
|
(do
|
|
(reset-functions!)
|
|
|
|
(is (valid-function-handler?
|
|
(deftwigfn "identity" [x]
|
|
x)))
|
|
(is (true? (function-exists? "identity")))
|
|
(is (valid-function-handler?
|
|
(deftwigfn "typename" [x]
|
|
(.getName (type x)))))
|
|
(is (true? (function-exists? "typename")))
|
|
|
|
; verify that the clojure function recognizes the correct types when the variable is passed via the model-map
|
|
(is (= (render "{{typename(x)}}" {:x 42})
|
|
"java.lang.Long")
|
|
"integer typename via model-map")
|
|
(is (= (render "{{typename(x)}}" {:x 3.14})
|
|
"java.lang.Double")
|
|
"float typename via model-map")
|
|
(is (= (render "{{typename(x)}}" {:x "foobar"})
|
|
"java.lang.String")
|
|
"string typename via model-map")
|
|
(is (= (render "{{typename(x)}}" {:x \a})
|
|
"java.lang.Character")
|
|
"char typename via model-map")
|
|
(is (= (render "{{typename(x)}}" {:x true})
|
|
"java.lang.Boolean")
|
|
"boolean typename via model-map")
|
|
(is (= (render "{{typename(x)}}" {:x '(1 2 3 4 5)})
|
|
"clojure.lang.LazySeq")
|
|
"list typename via model-map")
|
|
(is (= (render "{{typename(x)}}" {:x [1 2 3 4 5]})
|
|
"clojure.lang.LazySeq")
|
|
"vector typename via model-map")
|
|
(is (= (render "{{typename(x)}}" {:x {:a 1 :b "foo" :c nil}})
|
|
"clojure.lang.PersistentArrayMap")
|
|
"map typename via model-map")
|
|
(is (= (render "{{typename(x)}}" {:x #{1 2 3 4 5}})
|
|
"clojure.lang.LazySeq")
|
|
"set typename via model-map")
|
|
|
|
; verify that the clojure function recognizes the correct types when the variable is passed via a constant
|
|
; value embedded in the template
|
|
(is (= (render "{{typename(42)}}" nil)
|
|
"java.lang.Integer")
|
|
"integer typename via constant value embedded in the template")
|
|
(is (= (render "{{typename(3.14)}}" nil)
|
|
"java.lang.Double")
|
|
"float typename via constant value embedded in the template")
|
|
(is (= (render "{{typename('foobar')}}" nil)
|
|
"java.lang.String")
|
|
"string typename via constant value embedded in the template")
|
|
(is (= (render "{{typename('a')}}" nil)
|
|
"java.lang.Character")
|
|
"char typename via constant value embedded in the template")
|
|
(is (= (render "{{typename(true)}}" nil)
|
|
"java.lang.Boolean")
|
|
"boolean typename via constant value embedded in the template")
|
|
(is (= (render "{{typename([1, 2, 3, 4, 5])}}" nil)
|
|
"clojure.lang.LazySeq")
|
|
"list typename via constant value embedded in the template")
|
|
(is (= (render "{{typename(1..5)}}" nil)
|
|
"clojure.lang.LazySeq")
|
|
"vector typename via constant value embedded in the template")
|
|
(is (= (render "{{typename({a: 1, b: 'foo', c: null})}}" nil)
|
|
"clojure.lang.PersistentArrayMap")
|
|
"map typename via constant value embedded in the template")
|
|
|
|
; simple passing / returning... not doing anything exciting with the arguments
|
|
; using a constant value embedded inside the template
|
|
(is (= (render "{{identity(x)}}" {:x 42})
|
|
"42")
|
|
"integer via model-map")
|
|
(is (= (render "{{identity(x)}}" {:x 3.14})
|
|
"3.14")
|
|
"float via model-map")
|
|
(is (= (render "{{identity(x)}}" {:x "foobar"})
|
|
"foobar")
|
|
"string via model-map")
|
|
(is (= (render "{{identity(x)}}" {:x \a})
|
|
"a")
|
|
"char via model-map")
|
|
(is (= (render "{{identity(x)}}" {:x true})
|
|
"true")
|
|
"boolean via model-map")
|
|
(is (= (render "{{identity(x)}}" {:x '(1 2 3 4 5)})
|
|
"[1, 2, 3, 4, 5]")
|
|
"list via model-map")
|
|
(is (= (render "{{identity(x)}}" {:x [1 2 3 4 5]})
|
|
"[1, 2, 3, 4, 5]")
|
|
"vector via model-map")
|
|
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
|
(is (= (render "{{identity(x)}}" {:x {:a 1 :b "foo" :c nil}})
|
|
"{b=foo, c=null, a=1}")
|
|
"map via model-map")
|
|
; TODO: fix test, set->vector conversion performs an iteration through the set (the order is undefined)
|
|
(is (= (render "{{identity(x)}}" {:x #{1 2 3 4 5}})
|
|
"[1, 4, 3, 2, 5]")
|
|
"set via model-map")
|
|
|
|
; simple passing / returning... not doing anything exciting with the arguments
|
|
; using a constant value embedded inside the template
|
|
(is (= (render "{{identity(42)}}" nil)
|
|
"42")
|
|
"integer via constant value embedded in the template")
|
|
(is (= (render "{{identity(3.14)}}" nil)
|
|
"3.14")
|
|
"float via constant value embedded in the template")
|
|
(is (= (render "{{identity('foobar')}}" nil)
|
|
"foobar")
|
|
"string via constant value embedded in the template")
|
|
(is (= (render "{{identity('a')}}" nil)
|
|
"a")
|
|
"char via constant value embedded in the template")
|
|
(is (= (render "{{identity(true)}}" nil)
|
|
"true")
|
|
"boolean via constant value embedded in the template")
|
|
(is (= (render "{{identity([1, 2, 3, 4, 5])}}" nil)
|
|
"[1, 2, 3, 4, 5]")
|
|
"enumerated list via constant value embedded in the template")
|
|
(is (= (render "{{identity(1..5)}}" nil)
|
|
"[1, 2, 3, 4, 5]")
|
|
"list by comprehension via constant value embedded in the template")
|
|
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
|
(is (= (render "{{identity({a: 1, b: 'foo', c: null})}}" nil)
|
|
"{b=foo, c=null, a=1}")
|
|
"map via constant value embedded in the template")
|
|
|
|
; iterating over passed sequence/collection type arguments passed to a custom function from a variable
|
|
; inside the model-map and being returned
|
|
(is (= (render "{% for i in identity(x) %}{{i}} {% endfor %}" {:x '(1 2 3 4 5)})
|
|
"1 2 3 4 5 ")
|
|
"list (iterating over a model-map var passed to a function and returned from it)")
|
|
(is (= (render "{% for i in identity(x) %}{{i}} {% endfor %}" {:x [1 2 3 4 5]})
|
|
"1 2 3 4 5 ")
|
|
"vector (iterating over a model-map var passed to a function and returned from it)")
|
|
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
|
(is (= (render "{% for k, v in identity(x) %}{{k}}: {{v}} {% endfor %}" {:x {:a 1 :b "foo" :c nil}})
|
|
"b: foo c: null a: 1 ")
|
|
"map (iterating over a model-map var passed to a function and returned from it)")
|
|
(is (= (render "{% for i in identity(x) %}{{i}} {% endfor %}" {:x #{1 2 3 4 5}})
|
|
"1 4 3 2 5 ")
|
|
"set (iterating over a model-map var passed to a function and returned from it)")
|
|
|
|
; iterating over passed sequence/collection type arguments passed to a custom function from a constant
|
|
; value embedded in the template and being returned
|
|
(is (= (render "{% for i in identity([1, 2, 3, 4, 5]) %}{{i}} {% endfor %}" nil)
|
|
"1 2 3 4 5 ")
|
|
"enumerated list (iterating over a model-map var passed to a function and returned from it)")
|
|
(is (= (render "{% for i in identity(1..5) %}{{i}} {% endfor %}" nil)
|
|
"1 2 3 4 5 ")
|
|
"list by comprehension (iterating over a model-map var passed to a function and returned from it)")
|
|
; TODO: order of iteration through a map is undefined, the string being tested may not always be the same (wrt. order)
|
|
(is (= (render "{% for k, v in identity({a: 1, b: 'foo', c: null}) %}{{k}}: {{v}} {% endfor %}" nil)
|
|
"b: foo c: null a: 1 ")
|
|
"map (iterating over a model-map var passed to a function and returned from it)")
|
|
|
|
(reset-functions!))))
|
|
|
|
(deftest standard-functions
|
|
(testing "Standard functions were added properly"
|
|
(is (true? (function-exists? "blank_if_null")))
|
|
(is (true? (function-exists? "butlast")))
|
|
(is (true? (function-exists? "dump")))
|
|
(is (true? (function-exists? "dump_table")))
|
|
(is (true? (function-exists? "nth")))
|
|
(is (true? (function-exists? "max")))
|
|
(is (true? (function-exists? "min")))
|
|
(is (true? (function-exists? "random")))
|
|
(is (true? (function-exists? "range")))
|
|
(is (true? (function-exists? "rest")))
|
|
(is (true? (function-exists? "second")))
|
|
(is (true? (function-exists? "sort")))
|
|
(is (true? (function-exists? "sort_descending")))
|
|
(is (true? (function-exists? "sort_by")))
|
|
(is (true? (function-exists? "sort_descending_by"))))
|
|
|
|
(testing "blank_if_null"
|
|
(is (= (render "{{ a|blank_if_null }}" nil)
|
|
""))
|
|
(is (= (render "{{ a|blank_if_null }}" {:a nil})
|
|
""))
|
|
(is (= (render "{{ a|blank_if_null }}" {:a "foo"})
|
|
"foo"))
|
|
(is (= (render "{{ a|nonull }}" nil)
|
|
"")))
|
|
|
|
(testing "butlast"
|
|
(is (= (render "{{ [1, 2, 3, 4, 5]|butlast }}" nil)
|
|
"[1, 2, 3, 4]")))
|
|
|
|
(testing "center"
|
|
(is (= (render "{{ center('bat', 5) }}" nil)
|
|
" bat "))
|
|
(is (= (render "{{ center('bat', 3) }}" nil)
|
|
"bat"))
|
|
(is (= (render "{{ center('bat', 5, 'x') }}" nil)
|
|
"xbatx")))
|
|
|
|
(testing "contains"
|
|
(is (= (render "{{ {a: 1, b: 2, c: 3}|contains(\"b\") }}" nil)
|
|
"true"))
|
|
(is (= (render "{{ {a: 1, b: 2, c: 3}|contains(\"d\") }}" nil)
|
|
"false"))
|
|
(is (= (render "{{ [1, 2, 3, 4]|contains(2) }}" nil)
|
|
"true"))
|
|
(is (= (render "{{ [1, 2, 3, 4]|contains(5) }}" nil)
|
|
"false"))
|
|
(is (= (render "{{ \"abcdef\"|contains(\"abc\") }}" nil)
|
|
"true"))
|
|
(is (= (render "{{ \"abcdef\"|contains(\"xyz\") }}" nil)
|
|
"false")))
|
|
|
|
(testing "dump"
|
|
(is (= (render "{{ a|dump }}" {:a [{:foo "bar"} [1, 2, 3] "hello"]})
|
|
"({:foo \"bar\"} (1 2 3) \"hello\")\n")))
|
|
|
|
(testing "dump_table"
|
|
(is (= (render "{{ t|dump_table }}", {:t [{:a 1 :b 2 :c 3} {:b 5 :a 7 :c "dog"}]})
|
|
"\n| :a | :b | :c |\n|----+----+-----|\n| 1 | 2 | 3 |\n| 7 | 5 | dog |\n")))
|
|
|
|
(testing "index_of"
|
|
(is (= (render "{{ [1, 2, 3, 2, 1]|index_of(2) }}" nil)
|
|
"1"))
|
|
(is (= (render "{{ [1, 2, 3, 2, 1]|index_of(5) }}" nil)
|
|
"-1"))
|
|
(is (= (render "{{ \"abcdcba\"|index_of(\"b\") }}" nil)
|
|
"1"))
|
|
(is (= (render "{{ \"abcdcba\"|index_of(\"z\") }}" nil)
|
|
"-1")))
|
|
|
|
(testing "last_index_of"
|
|
(is (= (render "{{ [1, 2, 3, 2, 1]|last_index_of(2) }}" nil)
|
|
"3"))
|
|
(is (= (render "{{ [1, 2, 3, 2, 1]|last_index_of(5) }}" nil)
|
|
"-1"))
|
|
(is (= (render "{{ \"abcdcba\"|last_index_of(\"b\") }}" nil)
|
|
"5"))
|
|
(is (= (render "{{ \"abcdcba\"|last_index_of(\"z\") }}" nil)
|
|
"-1")))
|
|
|
|
(testing "max"
|
|
(is (= (render "{{ [2, 1, 5, 3, 4]|max }}" nil)
|
|
"5"))
|
|
(is (= (render "{{ max(2, 1, 5, 3, 4) }}" nil)
|
|
"5")))
|
|
|
|
(testing "min"
|
|
(is (= (render "{{ [2, 1, 5, 3, 4]|min }}" nil)
|
|
"1"))
|
|
(is (= (render "{{ min(2, 1, 5, 3, 4) }}" nil)
|
|
"1")))
|
|
|
|
(testing "normalize_space"
|
|
(is (= (render "{{ normalize_space(' hello world ') }}" nil)
|
|
"hello world")))
|
|
|
|
(testing "nth"
|
|
(is (= (render "{{ [1, 2, 3, 4, 5]|nth(2) }}" nil)
|
|
"3"))
|
|
(is (thrown-with-msg?
|
|
Exception
|
|
#"java.lang.IndexOutOfBoundsException"
|
|
(render "{{ [1, 2, 3, 4, 5]|nth(6) }}" nil)))
|
|
(is (= (render "{{ [1, 2, 3, 4, 5]|nth(6, \"not found\") }}" nil)
|
|
"not found")))
|
|
|
|
(testing "pad_left"
|
|
(is (= (render "{{ pad_left('bat', 5) }}" nil)
|
|
" bat"))
|
|
(is (= (render "{{ pad_left('bat', 3) }}" nil)
|
|
"bat"))
|
|
(is (= (render "{{ pad_left('bat', 5, 'x') }}" nil)
|
|
"xxbat")))
|
|
|
|
(testing "pad_right"
|
|
(is (= (render "{{ pad_right('bat', 5) }}" nil)
|
|
"bat "))
|
|
(is (= (render "{{ pad_right('bat', 3) }}" nil)
|
|
"bat"))
|
|
(is (= (render "{{ pad_right('bat', 5, 'x') }}" nil)
|
|
"batxx")))
|
|
|
|
(testing "random"
|
|
(is (some #{(render "{{ ['apple', 'orange', 'citrus']|random }}" nil)}
|
|
["apple" "orange" "citrus"]))
|
|
(is (some #{(render "{{ \"ABC\"|random }}" nil)}
|
|
["A" "B" "C"])))
|
|
|
|
(testing "range"
|
|
(is (= (render "{{ range(1, 5) }}" nil)
|
|
"[1, 2, 3, 4]"))
|
|
(is (= (render "{{ range(1, 5, 2) }}" nil)
|
|
"[1, 3]")))
|
|
|
|
(testing "repeat"
|
|
(is (= (render "{{ repeat('x', 10) }}" nil)
|
|
"xxxxxxxxxx"))
|
|
(is (= (render "{{ repeat('x', 0) }}" nil)
|
|
"")))
|
|
|
|
(testing "rest"
|
|
(is (= (render "{{ [1, 2, 3, 4, 5]|rest }}" nil)
|
|
"[2, 3, 4, 5]")))
|
|
|
|
(testing "second"
|
|
(is (= (render "{{ [1, 2, 3, 4, 5]|second }}" nil)
|
|
"2")))
|
|
|
|
(testing "sort"
|
|
(is (= (render "{{ [2, 1, 5, 3, 4]|sort }}" nil)
|
|
"[1, 2, 3, 4, 5]")))
|
|
|
|
(testing "sort_descending"
|
|
(is (= (render "{{ [2, 1, 5, 3, 4]|sort_descending }}" nil)
|
|
"[5, 4, 3, 2, 1]")))
|
|
|
|
(testing "sort_by"
|
|
(is (= (render "{{ [{a: 2}, {a: 1}, {a: 5}, {a: 3}, {a: 4}]|sort_by(\"a\") }}" nil)
|
|
"[{a=1}, {a=2}, {a=3}, {a=4}, {a=5}]")))
|
|
|
|
(testing "sort_descending_by"
|
|
(is (= (render "{{ [{a: 2}, {a: 1}, {a: 5}, {a: 3}, {a: 4}]|sort_descending_by(\"a\") }}" nil)
|
|
"[{a=5}, {a=4}, {a=3}, {a=2}, {a=1}]")))
|
|
|
|
(testing "wrap"
|
|
(is (= (render "{{ wrap(\"Here is one line of text that is going to be wrapped after 20 columns.\", 20) }}" nil)
|
|
"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."))
|
|
(is (= (render "{{ wrap(\"Here is one line of text that is going to be wrapped after 20 columns.\", 20, false, \"<br />\") }}" nil)
|
|
"Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."))
|
|
(is (= (render "{{ wrap(\"Click here to jump to the commons website - http://commons.apache.org\", 20, false) }}" nil)
|
|
"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"))
|
|
(is (= (render "{{ wrap(\"Click here to jump to the commons website - http://commons.apache.org\", 20, true) }}" nil)
|
|
"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"))))
|