/
core.clj
169 lines (137 loc) · 5.37 KB
/
core.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
(ns cljest.core
(:require [cljest.compilation.config :as config]
[cljest.format :as format]
[cljs.analyzer.api :as analyzer.api]
cljs.env))
(def ^:private user-defined-formatters-ns (some-> (config/get-config!)
(get :formatters-ns)
symbol))
(when user-defined-formatters-ns
(require `[~user-defined-formatters-ns]))
(defmacro describe
"Describes a block of tests. Any `before-each`/`after-each` inside of this block will be scoped to it.
Example:
```
(describe \"some-fn\"
(before-each (do-something))
(after-each (cleanup))
(it \"does something\" ...))
```"
[name & body]
`(js/describe ~name (fn []
~@body
js/undefined)))
(defmacro ^:private base-it
"Macro that handles any `it` like functions from Jest. For example, `it` and `it.only`.
See `it` and `only` below."
[f description & body]
(let [all-but-last-calls (drop-last body)
last-call (last body)]
`(~f ~description (fn []
; This is usually enforced with eslint, but we can't use it in cljs-land, so instead we enforce it using expect
(js/expect.hasAssertions)
~@all-but-last-calls
; Jest only allows undefined and Promise instances to be returned from the body of `it`. However, we also
; want to allow `nil`, so if the last call inside of the `it` call is `nil`, return `js/undefined` instead.
(let [last-call-result# ~last-call]
(if (nil? last-call-result#)
js/undefined
last-call-result#))))))
(defmacro it
"A single test case."
[name & body]
`(base-it js/test ~name ~@body))
(defmacro only
"Like `it` but is the only test that runs in this file."
[name & body]
`(base-it js/test.only ~name ~@body))
(defmacro todo
"Similar to a semi TODO, allows writing the name of a test case before writing the actual case."
[name]
`(js/it.todo ~name))
(defmacro skip
"Like `it`, but skips the test. Can be used to temporarily skip flaky tests."
[name & body]
`(base-it js/test.skip ~name ~@body))
(defmacro before-each
"Runs `body` before each test in the current scope."
[& body]
`(js/beforeEach (fn [] ~@body)))
(defmacro after-each
"Runs `body` after each test in the current scope."
[& body]
`(js/afterEach (fn [] ~@body)))
(defmacro before-all
"Runs `body` before all tests in the current file."
[& body]
`(js/beforeAll (fn [] ~@body)))
(defmacro after-all
"Runs `body` after all tests in the current file."
[& body]
`(js/afterAll (fn [] ~@body)))
(defmacro each
"A nicer way to create the same test case for multiple inputs. Essentially `doseq` for
test cases, except the second argument is the description passed into the generated `it`
block.
Example:
```
(each [[text num] [\"hello\" 1]
[\"world\" 2]
[\"foo\" 3]]
(str \"should call the function with \" text \" and \" num)
(do-something-with text num))
```
Above generates:
```
(it \"should call the function with hello and 1\" (do-something-with \"hello\" 1))
(it \"should call the function with world and 2\" (do-something-with \"world\" 2))
(it \"should call the function with foo and 3\" (do-something-with \"foo\" 3))
```"
[seq-exprs name & body]
`(do
(doseq ~seq-exprs (it ~name ~@body))
; Return `js/undefined` since `doseq` returns nil and describe blocks expect nothing to be returned
; from them.
js/undefined))
(defmacro only-each
"A combination of `each` and `only`."
[seq-exprs name & body]
`(do
(doseq ~seq-exprs (only ~name ~@body))
js/undefined))
(defn ^:private value->resolved-sym
"Resolves the given value to its fully qualified name. If it's a primitive (like true, false, a number) returns
'primitive."
[env value]
(if (symbol? value)
(get (analyzer.api/resolve env value) :name 'unknown-symbol)
'primitive))
(defmacro ^:private primitive-is
[form negated?]
(let [resolved-sym (value->resolved-sym &env form)]
`(binding [cljest.core/*inside-is?* true]
(cljest.core/is-matcher #(do ~form) ~(format/formatter resolved-sym form negated?)))))
(defmacro ^:private complex-is
[forms]
(let [negated? (= 'not (first forms))
body (if negated?
(second forms)
forms)
resolved-sym (if (seq? body)
(value->resolved-sym &env (first body))
(value->resolved-sym &env body))]
;; For the actual assertion, we want the full body, but for the formatter, we want to pass the possibly inner part
;; of (not (...)) to simplify writing the macro.
`(binding [cljest.core/*inside-is?* true
cljest.core/*is-body-negated?* ~negated?]
(cljest.core/is-matcher #(do ~forms) ~(format/formatter resolved-sym body negated?)))))
(defmacro is
"A generic assertion macro for Jest. Asserts that `form` is truthy.
Note: This does not work exactly like `clojure.test/is`. It does not accept `thrown?` or `thrown-with-msg?`.
Example:
(it \"should be true\"
(is (= true (my-fn :some-keyword)))"
[form]
(if (seq? form)
`(complex-is ~form)
`(primitive-is ~form false)))