I am considering porting my clojure-only test framework #lazytest to Clojurescript. I don't have a ton of experience with Clojurescript's compilation model or runtime (or lack thereof?), so are there any resources on such topics?
just look at the cljs.test port in ClojureScript
the finicky part is the async support
I wouldn't recommend looking at cljs.test. its kinda of a horrible port and way too macro heavy when it doesn't really need to be
depends on what you mean by "doesn't need to be" lol
well, for one you will need to "collect" your tests somehow, maybe filter them and then run them
the collect parts, as in find the vars/metadata must be a macro. the rest can just be functions. cljs.test, for example run-test complects those together, so everything is a macro, and everything it wants to use thus also becomes a macro
so, if you keep them separate you can make your life easier 😛
if you want an example how thats handled in shadow-cljs then there is only this macro for finding all the tests https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/test/env.clj
run-tests then becomes https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/test.cljs#L119 instead of https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/test.cljc#L308
not perfect either but all those damn macros make it very hard to work with cljs.test from the tool side 😛
interesting. in lazytest, all of the macros exist for defining the test suites and test cases and assertions. everything else (finding logic, the runner, filtering, printing, collecting results) is plain functions
i'll have to read your code to see what direction to take with the cljs version
yep, thats the same. this just uses cljs.test/deftest as normal for defining things
if you have a similar macro and that just registers each test with some place other than metadata, then you already have everything you need
nah, it's all var metadata 🥲
as in something like (do (defn this-is-the-test [..]) (your.test.thing/register "the.name.or.something" this-is-the-test {:extra "data"})) or so emitted by the macro
that stuff you could lookup fine at runtime if that just puts it into an atom or so
completely fine to do for CLJ as well
i'd prefer to stick with vars because that keeps side effects low
using the var collection and modifying it vs your own collection doesn't seem like more side effecty to me, but 🤷
well, with vars you have the support of all existing clojure functionality, which i guess keeps it from clojurescript
you can reload, you can rebind or alter, you can pass around as a first class object, you can inspect namespaces, etc
and all tooling and editors already support them
when i was looking for clojure.test alternatives, i looked at midje and speclj but neither is based on vars so you can't do repl driven development
the cljs compiler converts cljs -> js. something else actually runs it. the only limitation this implies is in how macros are handled, so they run in CLJ during compilation still, but can't have interspersed runtime access and evaluate or access those parts.
right, i more mean that (for example) there's no vars in clojurescript, so you can't say (require 'some-namespace) (ns-interns (the-ns 'some-namespace)) to get the public vars of a namespace, which fundamentally changes how you might approach writing a framework (like lazytest)
true, but you can get most of the similar data via the cljs.analyzer.api helper functions
hah perfect. is there documentation on that stuff somewhere? or an article on it or do i just have to read the cljs code directly?
I'd assume the api docs are available somewhere, but not actually sure. also this will be CLJ code 😉
but the API is intentially designed to look like their CLJ counterparts, so the usual all-ns ns-publics etc are all there
https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/analyzer/api.cljc#L213
i'm reading through now, thank you
Is there some way to reify js interfaces? I'd want to mock localStorage in tests by creating a js/Storage instance, but just reifying that with the correct methods doesn't seem to work (results in method is not a function errors)
Thanks!
no such thing as JS interfaces really. typically just an object with functions is enough. (def mystorage #js {:setItem (fn [key val]) :getItem (fn [key]))
or (deftype MyStorage [] Object (setItem [this key val]) (getItem [this key]))
is there some advantage to either of those approaches?
one has the implicit constructor/this, the other is just two functions I guess
(deftype MyStorage [data]
Object
(setItem [this key val]
(unchecked-set data key val))
(getItem [this key]
(unchecked-get data key)))
(def mystorage (MyStorage. #js {}))