clojurescript

2025-06-25T14:40:29.494659Z

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?

borkdude 2025-06-26T08:41:06.050869Z

just look at the cljs.test port in ClojureScript

borkdude 2025-06-26T08:41:16.924649Z

the finicky part is the async support

thheller 2025-06-26T08:42:25.973619Z

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

2025-06-26T11:51:08.925319Z

depends on what you mean by "doesn't need to be" lol

thheller 2025-06-26T12:06:55.273309Z

well, for one you will need to "collect" your tests somehow, maybe filter them and then run them

thheller 2025-06-26T12:09:16.606479Z

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

thheller 2025-06-26T12:09:43.557959Z

so, if you keep them separate you can make your life easier 😛

thheller 2025-06-26T12:10:52.898989Z

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

thheller 2025-06-26T12:12:44.060859Z

not perfect either but all those damn macros make it very hard to work with cljs.test from the tool side 😛

2025-06-26T12:13:56.345649Z

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

2025-06-26T12:14:21.703349Z

i'll have to read your code to see what direction to take with the cljs version

thheller 2025-06-26T12:15:09.407249Z

yep, thats the same. this just uses cljs.test/deftest as normal for defining things

👍 1
thheller 2025-06-26T12:15:34.836659Z

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

2025-06-26T12:16:01.313539Z

nah, it's all var metadata 🥲

thheller 2025-06-26T12:16:39.917989Z

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

thheller 2025-06-26T12:17:05.410799Z

that stuff you could lookup fine at runtime if that just puts it into an atom or so

thheller 2025-06-26T12:17:15.408579Z

completely fine to do for CLJ as well

2025-06-26T12:19:45.698969Z

i'd prefer to stick with vars because that keeps side effects low

thheller 2025-06-26T12:21:37.369489Z

using the var collection and modifying it vs your own collection doesn't seem like more side effecty to me, but 🤷

2025-06-26T12:37:21.538359Z

well, with vars you have the support of all existing clojure functionality, which i guess keeps it from clojurescript

2025-06-26T12:37:59.726709Z

you can reload, you can rebind or alter, you can pass around as a first class object, you can inspect namespaces, etc

2025-06-26T12:38:20.203249Z

and all tooling and editors already support them

2025-06-26T12:56:56.275999Z

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

thheller 2025-06-25T14:42:11.712639Z

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.

2025-06-25T15:53:28.288809Z

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)

thheller 2025-06-25T15:54:10.537869Z

true, but you can get most of the similar data via the cljs.analyzer.api helper functions

2025-06-25T15:55:56.712739Z

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?

thheller 2025-06-25T15:57:19.862589Z

I'd assume the api docs are available somewhere, but not actually sure. also this will be CLJ code 😉

thheller 2025-06-25T15:58:44.937679Z

but the API is intentially designed to look like their CLJ counterparts, so the usual all-ns ns-publics etc are all there

2025-06-25T15:58:59.687849Z

i'm reading through now, thank you

valerauko 2025-06-25T04:21:16.462499Z

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)

valerauko 2025-06-25T12:00:31.149719Z

Thanks!

thheller 2025-06-25T04:45:12.893809Z

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]))

thheller 2025-06-25T04:45:48.139559Z

or (deftype MyStorage [] Object (setItem [this key val]) (getItem [this key]))

valerauko 2025-06-25T05:06:43.315759Z

is there some advantage to either of those approaches?

thheller 2025-06-25T05:11:21.890749Z

one has the implicit constructor/this, the other is just two functions I guess

thheller 2025-06-25T05:12:37.878039Z

(deftype MyStorage [data]
  Object
  (setItem [this key val]
    (unchecked-set data key val))
  (getItem [this key]
    (unchecked-get data key)))

(def mystorage (MyStorage. #js {}))