From 91e2899d9015dbc5a822dd49f27b119bdeafe314 Mon Sep 17 00:00:00 2001 From: Kim Kinnear Date: Thu, 13 Aug 2020 13:41:52 -0400 Subject: [PATCH] Implement Clojurescript port for self-hosted Clojurescript. Requires planck. --- .gitignore | 1 + README.md | 44 ++++ deps.edn | 9 +- doc/cljdoc.edn | 1 + doc/getting-started-cljs.md | 197 ++++++++++++++++++ dooopts.edn | 1 + planckopts.edn | 1 + pom.xml | 16 +- .../clojure/{test.clj => test.cljc} | 147 +++++++++---- test/expectations/clojure/test_macros.cljc | 52 +++++ .../clojure/{test_test.clj => test_test.cljc} | 101 +++++---- 11 files changed, 474 insertions(+), 96 deletions(-) create mode 100644 doc/getting-started-cljs.md create mode 100644 dooopts.edn create mode 100644 planckopts.edn rename src/expectations/clojure/{test.clj => test.cljc} (79%) mode change 100755 => 100644 create mode 100644 test/expectations/clojure/test_macros.cljc rename test/expectations/clojure/{test_test.clj => test_test.cljc} (69%) diff --git a/.gitignore b/.gitignore index 75d3095..5715aac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .cpcache .clj-kondo/.cache *.jar +cljs-test-runner-out diff --git a/README.md b/README.md index 7e1713c..c3b74b1 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ and command-line tools. Works with Clojure 1.8 and later. Spec expectations are only available on Clojure 1.9 and later. +Works in self-hosted Clojurescript (specifically, +[`planck`](https://planck-repl.org)). See +[Getting Started with Clojurescript](/doc/getting-started-cljs.md) for details. + You can either use `deftest` from `clojure.test`, or `defexpect` from this library to wrap your tests. @@ -244,6 +248,46 @@ do done ``` +### Clojurescript testing + +The Clojurescript version requires self-hosted Clojurescript (specifically, +[`planck`](https://planck-repl.org)). Once you have `planck -h` working, +you can run the Clojurescript tests with: + +```clojure +clojure -A:cljs-runner -e :negative +``` +You can run the negative tests as well if you modify one line of `test.cljc`, +see the comments below the line containing `(def humane-test-output?`. + +#### Clojurescript REPL + +It can be handy to try things in a REPL. You can run a REPL for Clojurescript +by doing: +```clojure +$ planck --compile-opts planckopts.edn -c `clj -A:humane -Spath` -r +ClojureScript 1.10.520 +cljs.user=> (require '[expectations.clojure.test :refer-macros [defexpect expect]]) +nil +cljs.user=> (defexpect a (expect number? 1)) +#'cljs.user/a +cljs.user=> (a) +nil +cljs.user=> (defexpect a (expect number? :b)) +#'cljs.user/a +cljs.user=> (a) + +FAIL in (a) (run_block@file:44:173) + + +expected: (=? number? :b) + actual: (not (number? :b)) +nil +cljs.user=> +``` +This will set you up with `defexpect` and `expect`. Add others as required. + + ## License & Copyright Copyright © 2018-2020 Sean Corfield, all rights reserved. diff --git a/deps.edn b/deps.edn index 2fc3ed1..1c7904e 100755 --- a/deps.edn +++ b/deps.edn @@ -10,4 +10,11 @@ {:git/url "https://github.com/cognitect-labs/test-runner" :sha "f7ef16dc3b8332b0d77bc0274578ad5270fbfedd"}} :main-opts ["-m" "cognitect.test-runner" - "-d" "test"]}}} + "-d" "test"]} + :cljdoc {:extra-deps {planck {:mvn/version "2.23.0"}}} + :cljs-runner + {:extra-deps {olical/cljs-test-runner {:mvn/version "3.7.0"}, + pjstadig/humane-test-output {:mvn/version "0.10.0"}}, + :extra-paths ["src" "test" "cljs-test-runner-out/gen"], + :main-opts ["-m" "cljs-test-runner.main" "--doo-opts" + "dooopts.edn" "-x" "planck"]}}} diff --git a/doc/cljdoc.edn b/doc/cljdoc.edn index f3dc5e0..f097c69 100644 --- a/doc/cljdoc.edn +++ b/doc/cljdoc.edn @@ -1,6 +1,7 @@ {:cljdoc.doc/tree [["Readme" {:file "README.md"}] ["Changes" {:file "CHANGELOG.md"}] ["Getting Started" {:file "doc/getting-started.md"}] + ["Getting Started in Clojurescript" {:file "doc/getting-started-cljs.md"}] ["Collections" {:file "doc/collections.md"}] ["Useful Predicates" {:file "doc/useful-predicates.md"}] ["Expecting More" {:file "doc/more.md"}] diff --git a/doc/getting-started-cljs.md b/doc/getting-started-cljs.md new file mode 100644 index 0000000..5906464 --- /dev/null +++ b/doc/getting-started-cljs.md @@ -0,0 +1,197 @@ +# Getting Started with expectations/clojure-test using Clojurescript + +You can use `expectations/clojure-test` to run tests in both Clojure +and Clojurescript. Many tests will work without changes in both +Clojure and Clojurescript, though of course some will require +changes for the different environments. This section describes how +to use `expectations/clojure-test` in Clojurescript and the differences +from using it in Clojure -- see the other sections for details of how +to use it in Clojure for a complete picture. + + +## Installation + +In order to run `expectations/clojure-test` with Clojurescript, you +will use `olical/cljs-test-runner` and the Clojure tool `clj`. + +Your `deps.edn` should include this information: + +```clojure +{:aliases {:cljs-runner + {:extra-deps {expectations/cljc-test {:mvn/version "1.4.1"}, + olical/cljs-test-runner {:mvn/version "3.7.0"}, + pjstadig/humane-test-output {:mvn/version "0.10.0"}}, + :extra-paths ["src" "test" "cljs-test-runner-out/gen"], + :main-opts ["-m" "cljs-test-runner.main" "--doo-opts" + "dooopts.edn" "-x" "planck"]}}} +``` + +You will need two small `.edn` files in your project: + +`dooopts.edn`: +```clojure +{:paths {:planck "planck --compile-opts planckopts.edn"}} +``` + +`planckopts.edn`: +```clojure +{:warnings {:private-var-access false}} +``` + +To run the tests, you run: + +``` +clj -A:cljs-runner +``` + +These tests will take a good while longer to run than the same tests +in Clojure, so if you don't get any output for a while, that is not +necessarily a bad thing. + +### Requirements + +The Clojurescript version of `expectations/clojure-test` works (at present) +only with a specific implementation of self-hosted Clojurescript: +[`planck`](https://planck-repl.org). You will have to install `planck` +yourself in order to use `expectations/clojure-test` with Clojurescript. + +You will have to get `planck -h` to work locally. See +[here](https://planck-repl.org) for instructions on how to install +`planck` on a variety of systems. Planck `2.24.0` or later is required. + +### Humane Test Output + +The use of Paul Stadig's +[Humane Test Output](https://github.com/pjstadig/humane-test-output), is +optional for the Clojure version of `expectations/clojure-test` but it is +required for the Clojurescript version of `expectations/clojure-test`. + +## The Basics + +This example is the Clojurescript version of the quick comparison provided +for the Clojure version of `expectations/clojure-test`, and provides a quick +comparison with `clojure.test` (the tests match those in the [`clojure.test` +documentation](http://clojure.github.io/clojure/clojure.test-api.html)): + +```clojure +(require '[expectations.clojure.test :refer [defexpect expect expecting]]) + +(defexpect simple-test ; (deftest simple-test + (expect 4 (+ 2 2)) ; (is (= 4 (+ 2 2))) + (expect number? 256) ; (is (instance? Long 256)) + (expect (.startsWith "abcde" "ab")) ; (is (.startsWith "abcde" "ab")) + (expect ##Inf (/ 1 0)) ; (is (thrown? ArithmeticException (/ 1 0))) + (expecting "Arithmetic" ; (testing "Arithmetic" + (expecting "with positive integers" ; (testing "with positive integers" + (expect 5 (+ 2 2)) ; (is (= 4 (+ 2 2))) + (expect 7 (+ 3 4))) ; (is (= 7 (+ 3 4)))) + (expecting "with negative integers" ; (testing "with negative integers" + (expect -4 (+ -2 -2)) ; (is (= -4 (+ -2 -2))) + (expect -1 (+ 3 -4))))) ; (is (= -1 (+ 3 -4)))))) +``` + +The third example could also be written as follows, since `expect` +allows an arbitrary predicate in the "expected" position: + +```clojure + (expect #(.startsWith % "ab") "abcde") +``` + +Or like this, since `expect` allows a regular expression in the "expected" position: + +```clojure + (expect #"^ab" "abcde") +``` + +Both of these more accurately reflect an expectation on the actual +value `"abcde"`, that the string begins with `"ab"`, than the `is` +equivalent which has the actual value embedded in the test expression. +Separating the "expectation" (value or predicate) from the "actual" +expression being tested often makes the test much clearer. + +## Differences from the Clojure version of `expectations/clojure-test` + +Here is the list of features from Expectations supported by the +Clojure version of `expectations.clojure.test` where there are +differences in the Clojurescript implementation. + +### * Class test +Classes are different in Clojurescript. + +Classes are all different in Clojurescript, and in some cases things +that would be a class in Clojure are different in Clojurescript. For +instance, lists are a class: +```clojure +(defexpect class-test cljs.core/List '(a b c)) +``` +and this test passes. Strings, however, don't have an easily +discoverable type or class, and are better handled with a predicate: +```clojure +(defexpect string-class-test string? "abc") +``` +In general, the classes in Clojurescript will not be the same as +the classes in Clojure. You can do this to write a test that +will work in both environments: +```clojure +(defexpect both-class-test (expect (= (type "abc") (type "def")))) +``` +but you cannot write this: +``` +(defexpect bad-both-class-test (type "abc") (type "def")) +``` +because `(type "abc")` yields something that tests positive as a +`fn?`, causing expectations to think it is a predicate. Which, +as it happens, it is not. + +### * Exception test + +Exceptions are very different in Clojurescript from Clojure. + +The Clojure example: +```clojure +(defexpect divide-by-zero ArithmeticException (/ 12 0)) +``` +doesn't even throw an exception -- it returns `##Inf`. +You can do this for that situation: +```clojure +(defexpect divide-by-zero ##Inf (/ 12 0)) +``` +but be careful putting `##Inf` in a reader conditional, as some versions of +Clojure don't handle that well. But all of this is a bit off-topic, +as we are discussing exceptions. + +Exceptions certainly exist and can be thrown. You can throw pretty +much anything in Javascript. There is no `Throwable` class in +Clojurecript to distinguish things that can be thrown from anything +else. The only exception supported in `expectations/clojure-test` +in Clojurescript is where the exception is: `js/Error`. For example: +```clojure +(defexpect exception js/Error (count 5)) +``` +will pass, because `(count 5)` throws `js/Error`. + +### * `with-test` +There is no `with-test` in `cljs.test`, so it is not available in +`expectations/clojure-test`. + +### * Specs +Specs are always supported, and work equivalently to Clojure. + +# Useful Additional Information + +The end of the Clojure [Getting Started](/doc/getting-started.md) provides +additional information on how to use `expectations/clojure-test`, and most +of the information is directly applicable to using `expectations/clojure-test` +in Clojurescript as well. + +# Further Reading + +Expectations provides a lot more: + +* [Useful Predicates](/doc/useful-predicates.md) +* [Collections](/doc/collections.md) +* [Expecting More](/doc/more.md) +* [Expecting Side Effects](/doc/side-effects.md) +* [Fixtures & Focused Test Execution](/doc/fixtures-focus.md) + + diff --git a/dooopts.edn b/dooopts.edn new file mode 100644 index 0000000..4c8e3a7 --- /dev/null +++ b/dooopts.edn @@ -0,0 +1 @@ +{:debug false :paths {:planck "planck --compile-opts planckopts.edn"}} diff --git a/planckopts.edn b/planckopts.edn new file mode 100644 index 0000000..c738493 --- /dev/null +++ b/planckopts.edn @@ -0,0 +1 @@ +{:warnings {:private-var-access false}} diff --git a/pom.xml b/pom.xml index 281c52c..e88ae9a 100644 --- a/pom.xml +++ b/pom.xml @@ -2,8 +2,8 @@ 4.0.0 expectations - clojure-test - 1.2.1 + cljc-test + 1.3.1 exp-clojure-test A clojure.test-compatible version of the classic Expectations testing library. https://github.com/clojure-expectations/clojure-test @@ -30,6 +30,18 @@ clojure 1.8.0 + + pjstadig + humane-test-output + 0.10.0 + provided + + + planck + planck + 2.23.0 + provided + src diff --git a/src/expectations/clojure/test.clj b/src/expectations/clojure/test.cljc old mode 100755 new mode 100644 similarity index 79% rename from src/expectations/clojure/test.clj rename to src/expectations/clojure/test.cljc index 870514a..4e62697 --- a/src/expectations/clojure/test.clj +++ b/src/expectations/clojure/test.cljc @@ -5,12 +5,15 @@ This namespace should be used standalone, without requiring the 'expectations' namespace -- this provides a translation layer from Expectations syntax down - to `clojure.test` functionality. - - We do not support ClojureScript in `clojure.test` mode, sorry." + to `clojure.test` functionality." (:require [clojure.data :as data] [clojure.string :as str] - [clojure.test :as t])) + #?(:cljs [planck.core]) + #?(:clj [clojure.test :as t] + :cljs [cljs.test :include-macros true :as t]) + #?(:cljs [cljs.spec.alpha :as s]) + #?@(:cljs [pjstadig.humane-test-output pjstadig.util + pjstadig.print]))) (def humane-test-output? "If Humane Test Output is available, activate it, and enable compatibility @@ -18,15 +21,25 @@ This Var will be `true` if Humane Test Output is available and activated, otherwise it will be `nil`." - (try - (require 'pjstadig.humane-test-output) - ((resolve 'pjstadig.humane-test-output/activate!)) - true - (catch Throwable _))) + #?(:clj (try (require 'pjstadig.humane-test-output) + ((resolve 'pjstadig.humane-test-output/activate!)) + true + (catch Exception _)) + :cljs (do + (defmethod cljs.test/report [:cljs.test/default :fail] + [event] + (#'pjstadig.util/report- + (if (:diffs event) + event + (pjstadig.print/convert-event event)))) + ; This should be true for normal operation, false for testing + ; this framework and running the :negative tests. + true))) ;; stub functions for :refer compatibility: (defn- bad-usage [s] - `(throw (IllegalArgumentException. + `(throw (#?(:clj IllegalArgumentException. + :cljs js/Error.) (str ~s " should only be used inside expect")))) (defmacro in @@ -104,24 +117,30 @@ (defn ^:no-doc spec? [e] (and (keyword? e) - (try - (require 'clojure.spec.alpha) - (when-let [get-spec (resolve 'clojure.spec.alpha/get-spec)] - (boolean (get-spec e))) - (catch Throwable _)))) + #?(:clj (try (require 'clojure.spec.alpha) + (when-let [get-spec (resolve 'clojure.spec.alpha/get-spec)] + (boolean (get-spec e))) + (catch Throwable _)) + :cljs (boolean (s/get-spec e))))) ;; smart equality extension to clojure.test assertion -- if the expected form ;; is a predicate (function) then the assertion is equivalent to (is (e a)) ;; rather than (is (= e a)) and we need the type check done at runtime, not ;; as part of the macro translation layer -(defmethod t/assert-expr '=? [msg form] +(defmethod #?(:clj t/assert-expr + :cljs cljs.test$macros/assert-expr) + '=? + #?(:clj [msg form] :cljs [menv msg form]) ;; (is (=? val-or-pred expr)) (let [[_ e a form'] form conform? (spec? e)] `(let [e# ~e a# ~a - valid?# (when ~conform? (resolve 'clojure.spec.alpha/valid?)) - explain-str?# (when ~conform? (resolve 'clojure.spec.alpha/explain-str)) + valid?# (when ~conform? #?(:clj (resolve 'clojure.spec.alpha/valid?) + :cljs s/valid?)) + explain-str?# (when ~conform? + #?(:clj (resolve 'clojure.spec.alpha/explain-str) + :cljs s/explain-str)) r# (cond ~conform? (valid?# e# a#) (fn? e#) @@ -163,7 +182,11 @@ thread exceptions into code that can parse information out of them, to be used with various expect predicates." [form] - `(try ~form (catch Throwable t# t#))) + `(try ~form + (catch #?(:clj Throwable + :cljs :default) + t# + t#))) (defn ^:no-doc all-report "Given an atom in which to accumulate results, return a function that @@ -271,9 +294,13 @@ (if (map? e#) (let [submap# (select-keys a# (keys e#))] (t/is (~'=? e# submap# '~form) ~msg')) - (throw (IllegalArgumentException. "'in' requires map or sequence")))) + (throw (#?(:clj IllegalArgumentException. + :cljs js/Error.) + "'in' requires map or sequence")))) :else - (throw (IllegalArgumentException. "'in' requires map or sequence"))))) + (throw (#?(:clj IllegalArgumentException. + :cljs js/Error.) + "'in' requires map or sequence"))))) (and (sequential? e) (= 'more (first e))) (let [es (mapv (fn [e] `(expect ~e ~a ~msg ~ex? ~e')) (rest e))] @@ -296,12 +323,25 @@ (partition 2 (rest (rest e))))] `(let [~(second e) ~a] ~@es)) - (and ex? (symbol? e) (resolve e) (class? (resolve e))) - (if (isa? (resolve e) Throwable) - `(t/is (~'thrown? ~e ~a) ~msg') - `(t/is (~'instance? ~e ~a) ~msg')) - - (isa? (type e) java.util.regex.Pattern) + #?(:clj (and ex? (symbol? e) (resolve e) (class? (resolve e))) + :cljs (and ex? + (symbol? e) + (planck.core/find-var e) + (or (= 'js/Error e) + ; is it a symbol which is not a predicate? + (and (fn? (deref (planck.core/find-var e))) + (not= (pr-str (deref (planck.core/find-var e))) + "#object[Function]"))))) + #?(:clj (if (isa? (resolve e) Throwable) + `(t/is (~'thrown? ~e ~a) ~msg') + `(t/is (~'instance? ~e ~a) ~msg')) + :cljs (if (= 'js/Error e) + `(t/is (~'thrown? ~e ~a) ~msg') + `(t/is (~'instance? ~e ~a) ~msg'))) + + (isa? (type e) + #?(:clj java.util.regex.Pattern + :cljs (type #"regex"))) `(t/is (re-find ~e ~a) ~msg') :else @@ -356,7 +396,8 @@ by name will return `nil`." [fn-vec & forms] (when-not (vector? fn-vec) - (throw (IllegalArgumentException. + (throw (#?(:clj IllegalArgumentException. + :cljs js/Error.) "side-effects requires a vector as its first argument"))) (let [mocks (reduce (fn [m f-spec] (if (vector? f-spec) @@ -413,7 +454,7 @@ a-val (actual-fn x)] (t/is (= e-val a-val) (difference-fn e-val a-val)))))) -(defn use-fixtures +#?(:clj (defn use-fixtures "Wrap test runs in a fixture function to perform setup and teardown. Using a fixture-type of `:each` wraps every test individually, while `:once` wraps the whole run in a single function. @@ -432,22 +473,46 @@ (when-let [after (:after f)] (after))) f)) - fs))) + fs)))) + +#?(:cljs (defmacro use-fixtures + "Wrap test runs in a fixture function to perform setup and + teardown. Using a fixture-type of `:each` wraps every test + individually, while `:once` wraps the whole run in a single function. -(defn- from-clojure-test + Hands off to `cljs.test/use-fixtures`, also accepts hash maps with `:before` + and/or `:after` keys that specify 0-arity functions to invoke + before/after the test/run." + [fixture-type & fs] + `(cljs.test/use-fixtures ~fixture-type ~@fs))) + +(defn from-clojure-test "Intern the specified symbol from `clojure.test` as a symbol in `expectations.clojure.test` with the same value and metadata." [f] - (let [tf (symbol "clojure.test" (name f)) - v (resolve tf) - m (meta v)] - (intern 'expectations.clojure.test f (deref v)) - (alter-meta! (resolve f) - merge - (update m :doc str (str "\n\nImported from clojure.test."))))) + (let [tf (symbol #?(:clj "clojure.test" + :cljs "cljs.test") + (name f)) + v (#?(:clj resolve + :cljs planck.core/find-var) + tf) + m (meta v)] + (#?(:clj intern + :cljs planck.core/intern) + 'expectations.clojure.test + (with-meta f + (update m + :doc + str + (str #?(:clj "\n\nImported from clojure.test." + :cljs "\n\nImported from cljs.test")))) + (deref v)))) + ;; bring over other useful clojure.test functions: -(doseq [f '[run-all-tests run-tests - test-all-vars test-ns test-var test-vars - with-test]] +(doseq [f '[#?@(:clj [run-all-tests run-tests test-all-vars test-ns with-test]) + test-var test-vars]] (from-clojure-test f)) + +; For testing expectations itself in cljs +#?(:cljs (s/def ::small-value (s/and pos-int? #(< % 100)))) diff --git a/test/expectations/clojure/test_macros.cljc b/test/expectations/clojure/test_macros.cljc new file mode 100644 index 0000000..2802aa5 --- /dev/null +++ b/test/expectations/clojure/test_macros.cljc @@ -0,0 +1,52 @@ +;; copyright (c) 2019-2020 sean corfield, all rights reserved + +(ns expectations.clojure.test-macros + "Macros to support testing the testing framework." + (:require #?(:clj [clojure.test :refer [is do-report] :as t] + :cljs [cljs.test :refer [do-report assert-expr] + :refer-macros [is assert-expr] :as t]) + #?(:cljs [cljs.spec.alpha :as s]) + #?(:clj [expectations.clojure.test :as sut] + :cljs [expectations.clojure.test :include-macros true :as sut]))) + +(defmacro is-not' + "Construct a negative test for an expectation with a symbolic failure." + [expectation failure & [msg]] + `(let [results# (atom nil)] + (with-redefs [do-report (sut/all-report results#)] + ~expectation) + (t/is (some (fn [fail#] + (= '~failure (:actual fail#))) + (:fail @results#))) + (when ~msg + (t/is (some (fn [fail#] + (re-find ~msg (:message fail#))) + (:fail @results#)))))) + +(defmacro is-not + "Construct a negative test for an expectation with a value-based failure." + [expectation failure & [msg]] + `(let [results# (atom nil)] + (with-redefs [do-report (sut/all-report results#)] + ~expectation) + (t/is (some (fn [fail#] + (= ~failure (:actual fail#))) + (:fail @results#))) + (when ~msg + (t/is (some (fn [fail#] + (re-find ~msg (:message fail#))) + (:fail @results#)))))) + +(defmacro passes + "Construct a positive test for an expectation with a predicate-based success. + + This is needed for cases where a successful test wraps a failing behavior, + such as `thrown?`, i.e., `(expect ExceptionType actual)`" + [expectation success] + `(let [results# (atom nil)] + (with-redefs [do-report (sut/all-report results#)] + ~expectation) + (t/is (some (fn [pass#] + (~success (:actual pass#))) + (:pass @results#))))) + diff --git a/test/expectations/clojure/test_test.clj b/test/expectations/clojure/test_test.cljc similarity index 69% rename from test/expectations/clojure/test_test.clj rename to test/expectations/clojure/test_test.cljc index b3bee89..67df94a 100644 --- a/test/expectations/clojure/test_test.clj +++ b/test/expectations/clojure/test_test.cljc @@ -6,51 +6,24 @@ Tests marked `^:negative` will not pass with Humane Test Output enabled because it manipulates the report data which my `is-not` macros rely on." (:require [clojure.string :as str] - [clojure.test :refer [deftest is do-report testing]] - [expectations.clojure.test :as sut - ;; refer these purely to help clj-kondo: - :refer [from-each in more more-of]])) - -(defmacro is-not' - "Construct a negative test for an expectation with a symbolic failure." - [expectation failure & [msg]] - `(let [results# (atom nil)] - (with-redefs [do-report (sut/all-report results#)] - ~expectation) - (is (some (fn [fail#] - (= '~failure (:actual fail#))) - (:fail @results#))) - (when ~msg - (is (some (fn [fail#] - (re-find ~msg (:message fail#))) - (:fail @results#)))))) - -(defmacro is-not - "Construct a negative test for an expectation with a value-based failure." - [expectation failure & [msg]] - `(let [results# (atom nil)] - (with-redefs [do-report (sut/all-report results#)] - ~expectation) - (is (some (fn [fail#] - (= ~failure (:actual fail#))) - (:fail @results#))) - (when ~msg - (is (some (fn [fail#] - (re-find ~msg (:message fail#))) - (:fail @results#)))))) - -(defmacro passes - "Construct a positive test for an expectation with a predicate-based success. - - This is needed for cases where a successful test wraps a failing behavior, - such as `thrown?`, i.e., `(expect ExceptionType actual)`" - [expectation success] - `(let [results# (atom nil)] - (with-redefs [do-report (sut/all-report results#)] - ~expectation) - (is (some (fn [pass#] - (~success (:actual pass#))) - (:pass @results#))))) + #?(:clj [expectations.clojure.test-macros :refer + [is-not' is-not passes]] + :cljs [expectations.clojure.test-macros + :refer-macros [is-not' is-not passes]]) + + #?(:clj [clojure.test :refer [deftest is do-report testing]] + :cljs [cljs.test :include-macros true + :refer [do-report assert-expr] + :refer-macros [deftest is testing assert-expr + use-fixtures]]) + #?(:cljs [cljs.spec.alpha :as s]) + #?(:clj [expectations.clojure.test :refer + [from-each in more more-of] :as sut] + :cljs [expectations.clojure.test + :include-macros true + :as sut]))) + +; The macros are in test_macros.cljc to support Clojurescript. (deftest predicate-test (is (sut/expect even? (+ 1 1))) @@ -85,7 +58,7 @@ ;; TODO: fails because regexes never compare equal to themselves! #_(is-not' (sut/expect #"fool" "It's foobar!") (not (re-find #"fool" "It's foobar!")))) -(deftest exception-test +#?(:clj (deftest exception-test (passes (sut/expect ArithmeticException (/ 12 0)) (fn [ex] (let [t (Throwable->map ex)] @@ -93,11 +66,25 @@ (or (= 'java.lang.ArithmeticException (-> t :via first :type)) (= java.lang.ArithmeticException (-> t :via first :type)))))))) -(deftest class-test + :cljs (deftest cljs-exception-test + (passes (sut/expect js/Error (throw (ex-info "foo" {}))) + (fn [ex] + (let [t (cljs.repl/Error->map ex)] + (and (= "foo" + (-> t :cause)) (or (= 'ExceptionInfo + (->> t :via first :type)) + (= ExceptionInfo + (->> t :via first :type))))))))) + +#?(:clj (deftest class-test (is (sut/expect String (name :foo))) - (is-not (sut/expect String :foo) clojure.lang.Keyword)) + (is-not (sut/expect String :foo) clojure.lang.Keyword))) -(try +#?(:cljs (deftest class-test + (is (sut/expect cljs.core/List '(a b c))) + (is-not (sut/expect cljs.core/List :foo) cljs.core/Keyword))) + +#?(:clj (try (eval '(do (require '[clojure.spec.alpha :as s]) (s/def :small/value (s/and pos-int? #(< % 100))) @@ -105,7 +92,16 @@ (is (sut/expect :small/value (* 13 4))) (is-not' (sut/expect :small/value (* 13 40)) (not (=? :small/value 520)))))) (catch Throwable _ - (println "\nOmitting Spec tests for Clojure" (clojure-version)))) + (println "\nOmitting Spec tests for Clojure" (clojure-version))))) + +; Note :expectations.clojure.test/small-value is defined at the end of +; expectations.clojure.test/test.cljc for cljs testing. Defining it here +; does not work. +#?(:cljs (deftest spec-test + (is (sut/expect :expectations.clojure.test/small-value (* 13 4))) + (is-not' (sut/expect :expectations.clojure.test/small-value + (* 13 40)) + (not (=? :expectations.clojure.test/small-value 520))))) (deftest collection-test (is (sut/expect {:foo 1} (in {:foo 1 :cat 4}))) @@ -150,7 +146,8 @@ (def d-t-counter (atom 0)) -(sut/with-test +; There is no cljs.test/with-test +#?(:clj (sut/with-test (defn definition-test "Make sure expectations work with clojure.test/with-test." [a b c] @@ -161,7 +158,7 @@ (is (= 0 @d-t-counter)) (sut/expect 1 (definition-test 1 1 1)) (sut/expect 6 (definition-test 1 2 3)) - (is (= 2 @d-t-counter))) + (is (= 2 @d-t-counter)))) ;; these would be failing tests in 1.x but not in 2.x: (sut/defexpect deftest-equivalence-0)