Fork me on GitHub
#clojurescript
<
2023-03-19
>
Jakub Šťastný00:03:41

Hey guys. I'd appreciate your opinions on testing frameworks. I see there's https://clojurescript.org/tools/testing, I tend to use built-in tools (if they're worth it), but I wanted to inquire about what others are using (and why). In Ruby I was using rSpec, but I'm open to any approach, above all I'm looking for something idiomatic for CLJS, definitely with good error messages so it's easy to see what's going on.

hifumi12300:03:17

I always use the built-in testing library. Usually the macros like deftest, testing , is, and are. So do a lot of other Clojure projects IME. For CLJS in particular, it is a bit annoying to actually run tests, but if you’re using shadow-cljs then you can produce a single JS file for either Node or Karma to consume. I personally use Karma for automated browser tests

👍 4
Jakub Šťastný00:03:02

I already have tests that run in Deno and its built-in framework. These are integration tests (I'm making a JS library in CLJS), so these are to make sure the API works in JS as expected. On CLJS level I want to write unit tests so finding a bug is easier. I'm not worried about browser tests, there's no frontend in this (or shall we say it's not my problem at least).

skylize02:03:07

Pretty much anything you come across will be a very thin layer around Clojure.test. So you should do quite well to learn Clojure.test first and then use your own judgement to what offers enough value to add it. Unfortunately, Clojure.test lacks... shall we say "hooks" for modularity. So it's hard to deviate very far from it without starting over. For whatever reason, nobody has followed through on any real attempt to start from scratch. ( Kaocha is the closest thing; trying, at least in some ways, to truly rethink how Clojure testing should work. ) When you do get ready to upgrade, I don t think you will find much more than a few runners to for starting tests selectively and some is/are replacements to change the phrasing and style of your test code.

👍 2
Jakub Šťastný01:03:47

Blimey and for test runner? I can see that the testing API and the test runner are separated. My environment is ShadowCLJS + Deno. It'll run in the browser eventually, but since it's just calculations, I'm not overly bothered by testing in various browsers, there's no DOM, no UI, just pure math.

Jakub Šťastný02:03:02

Actually I might just go for https://github.com/cognitect-labs/test-runner. The code is just a bunch of math fns, it'll run works just fine on JVM as well and the test-runner seems way more mature than its CLJS alternatives.

seancorfield03:03:31

I've been using https://github.com/Olical/cljs-test-runner for all of my cljs testing for a while.

👍 2
2
Jakub Šťastný13:03:09

I see CLJS doesn't have (anymore) format. I can't see any replacement. I need to format months, so they always have 2 digits (`1` -> 01 for January etc).

p-himik13:03:55

(.padStart (str month) 2 "0")

👍 2
Jakub Šťastný13:03:21

Thanks @U2FRKM4TW. Is there any cross-platform (CLJS & CLJ) solution by any chance?

p-himik13:03:02

#?(:cljs (.padString (str month) 2 "0")
   :clj (String/format "%02d" month))
;) But nah, AFAIK there's nothing cross-platform that would even remotely justify its usage over the above code.

p-himik13:03:43

E.g. there's goog.format but you really shouldn't use it unless there's just no other choice. And it's not like it would make the above code any simpler - on the contrary.

Jakub Šťastný13:03:45

Yeah I thought I might have to resort to that.

Jakub Šťastný13:03:58

OK, thanks.

👍 2
tomd13:03:20

Not sure it meets @U2FRKM4TW's standards for remotely justifying its usage over the above code, but https://github.com/quoll/clormat may be worth a look.

👍 2
p-himik13:03:38

Yeah, I wouldn't use it for just padStart. Or even for something more complex. I would consider using something like this only if the format string is something I don't have statically in my CLJS code. Or, perhaps, if I have dozens upon dozens of format strings in CLJS for some weird reason.

kennytilton15:03:24

(cljs.pprint/cl-format nil "~2,'0d" 7)
=> "07"

kennytilton15:03:21

But I like .padStart! If I were doing more CLJ heads down I think I'd like to beef up my interop, take advantage of the underlying beast.

p-himik15:03:10

Ah, right, cl-format. I personally have 2 issues with it: • IME it's not worth learning for the vast majority of string-related functionality • Extra 100kB in the release bundle (`cljs.core` is 250kB) do not justify it Of course, there might be instances where it is justified. But I haven't seen them yet.

kennytilton16:03:22

"Extra 100kb..." ?! Better get that out of Web/MX! :rolling_on_the_floor_laughing:

🙂 2
hifumi12321:03:10

I was about to ask "no love for cl-format?" but @U0PUGPSFR came to the rescue 😁

2
hifumi12321:03:30

Even within the Common Lisp community FORMAT is a controversial function but I've always loved to use it and I'm always thankful it exists in clojure.pprint

kennytilton22:03:20

Don't make me post "99 Bottles of Beer On the Wall"!

Patrick Brown18:03:21

I've got a macro that defines a record that works in clojure, but causes an error in clojurescript. All my other macros in the same ns are working in clj and cljs, so I'm assuming it has something to do with the difference between https://github.com/clojure/clojure/blob/clojure-1.10.1/src/clj/clojure/core_deftype.clj#L312 and https://cljs.github.io/api/cljs.core/defrecord. Has anyone run into this? I'm passing a map to the macro and using a function to make a keyword into a symbol define the defrecord So in the error below ':dt/tag' becomes 'Tag'. Similar macros are creating other vars, only the defrecord macro fails. Any ideas what to do? Here is the error.

58 | (defattr {:group/ident {:spec :spec/group-kw, :ident? true, :sc [:one :kw :ident]}})
  59 | (defdt {:dt/tag {:req [:tag/ident], :dt/parent :dt/datatype, :dt/version 1}})
-------^------------------------------------------------------------------------
Encountered error when macroexpanding cljs.core/defrecord.
ClassCastException: class clojure.lang.Keyword cannot be cast to class clojure.lang.Symbol (clojure.lang.Keyword and clojure.lang.Symbol are in unnamed module of loader 'app')

thheller18:03:51

so what does it expand to?

thheller18:03:23

that is a clojure error, so placing a keyword where symbol should be somewhere

Patrick Brown18:03:32

That's the error I get from the shadow server while watching the build. In the .cljc namespace I get no error and map->Tag works with all the correct fields. I'll try to extract some. Here are the results of macroexpand and macroexpand-1 in clojure. Macroexpand-1

(macroexpand-1 '(defdt-record {:dt/tag {:req [:tag/ident], :dt/parent :dt/datatype, :dt/version 1}}))
(clojure.core/defrecord Tag [dt ident] :load-ns true)
MacroExpand
(let* [] (clojure.core/declare ->Tag) (clojure.core/declare map->Tag) (deftype* net.drilling.x/Tag net.drilling.x.Tag [dt ident __meta __extmap __hash __hasheq] :implements [clojure.lang.IRecord clojur\
e.lang.IHashEq clojure.lang.IObj clojure.lang.ILookup clojure.lang.IKeywordLookup clojure.lang.IPersistentMap java.util.Map java.io.Serializable] :load-ns true (clojure.core/entrySet [this__7899__auto_\
_] (clojure.core/set this__7899__auto__)) (clojure.core/values [this__7898__auto__] (clojure.core/vals this__7898__auto__)) (clojure.core/keySet [this__7897__auto__] (clojure.core/set (clojure.core/key\
s this__7897__auto__))) (clojure.core/clear [this__7896__auto__] (throw (java.lang.UnsupportedOperationException.))) (clojure.core/putAll [this__7894__auto__ m__7895__auto__] (throw (java.lang.Unsuppor\
tedOperationException.))) (clojure.core/remove [this__7892__auto__ k__7893__auto__] (throw (java.lang.UnsupportedOperationException.))) (clojure.core/put [this__7889__auto__ k__7890__auto__ v__7891__au\
to__] (throw (java.lang.UnsupportedOperationException.))) (clojure.core/get [this__7887__auto__ k__7888__auto__] (.valAt this__7887__auto__ k__7888__auto__)) (clojure.core/containsValue [this__7885__au\
to__ v__7886__auto__] (clojure.core/boolean (clojure.core/some #{v__7886__auto__} (clojure.core/vals this__7885__auto__)))) (clojure.core/isEmpty [this__7884__auto__] (clojure.core/= 0 (.count this__78\
84__auto__))) (clojure.core/size [this__7883__auto__] (.count this__7883__auto__)) (clojure.core/without [this__7881__auto__ k__7882__auto__] (if (clojure.core/contains? #{:dt :ident} k__7882__auto__) \
(clojure.core/dissoc (clojure.core/with-meta (clojure.core/into {} this__7881__auto__) __meta) k__7882__auto__) (new Tag dt ident __meta (clojure.core/not-empty (clojure.core/dissoc __extmap k__7882__a\
uto__))))) (clojure.core/assoc [this__7879__auto__ k__7880__auto__ G__33921] (clojure.core/condp clojure.core/identical? k__7880__auto__ :dt (new Tag G__33921 ident __meta __extmap) :ident (new Tag dt \
G__33921 __meta __extmap) (new Tag dt ident __meta (clojure.core/assoc __extmap k__7880__auto__ G__33921)))) (clojure.core/iterator [G__33921] (clojure.lang.RecordIterator. G__33921 [:dt :ident] (cloju\
re.lang.RT/iter __extmap))) (clojure.core/seq [this__7878__auto__] (clojure.core/seq (clojure.core/concat [(clojure.lang.MapEntry/create :dt dt) (clojure.lang.MapEntry/create :ident ident)] __extmap)))\
 (clojure.core/entryAt [this__7874__auto__ k__7875__auto__] (clojure.core/let [v__7876__auto__ (.valAt this__7874__auto__ k__7875__auto__ this__7874__auto__)] (clojure.core/when-not (clojure.core/ident\
ical? this__7874__auto__ v__7876__auto__) (clojure.lang.MapEntry/create k__7875__auto__ v__7876__auto__)))) (clojure.core/containsKey [this__7872__auto__ k__7873__auto__] (clojure.core/not (clojure.cor\
e/identical? this__7872__auto__ (.valAt this__7872__auto__ k__7873__auto__ this__7872__auto__)))) (clojure.core/equiv [this__7871__auto__ G__33921] (clojure.core/boolean (clojure.core/or (clojure.core/\
identical? this__7871__auto__ G__33921) (clojure.core/when (clojure.core/identical? (clojure.core/class this__7871__auto__) (clojure.core/class G__33921)) (clojure.core/let [G__33921 G__33921] (clojure\
.core/and (clojure.core/= dt (. G__33921 -dt)) (clojure.core/= ident (. G__33921 -ident)) (clojure.core/= __extmap (. G__33921 __extmap)))))))) (clojure.core/cons [this__7869__auto__ e__7870__auto__] (\
(var clojure.core/imap-cons) this__7869__auto__ e__7870__auto__)) (clojure.core/empty [this__7868__auto__] (throw (java.lang.UnsupportedOperationException. (clojure.core/str "Can't create empty: " "net\
.drilling.x.Tag")))) (clojure.core/count [this__7867__auto__] (clojure.core/+ 2 (clojure.core/count __extmap))) (clojure.core/getLookupThunk [this__7865__auto__ k__7866__auto__] (clojure.core/let [gcla\
ss (clojure.core/class this__7865__auto__)] (clojure.core/case k__7866__auto__ :dt (clojure.core/reify clojure.lang.ILookupThunk (clojure.core/get [thunk gtarget] (if (clojure.core/identical? (clojure.\
core/class gtarget) gclass) (. gtarget -dt) thunk))) :ident (clojure.core/reify clojure.lang.ILookupThunk (clojure.core/get [thunk gtarget] (if (clojure.core/identical? (clojure.core/class gtarget) gcl\
ass) (. gtarget -ident) thunk))) nil))) (clojure.core/valAt [this__7862__auto__ k__7863__auto__ else__7864__auto__] (clojure.core/case k__7863__auto__ :dt dt :ident ident (clojure.core/get __extmap k__\
7863__auto__ else__7864__auto__))) (clojure.core/valAt [this__7860__auto__ k__7861__auto__] (.valAt this__7860__auto__ k__7861__auto__ nil)) (clojure.core/withMeta [this__7859__auto__ G__33921] (new Ta\
g dt ident G__33921 __extmap __hash __hasheq)) (clojure.core/meta [this__7858__auto__] __meta) (clojure.core/equals [this__7857__auto__ G__33921] (clojure.lang.APersistentMap/mapEquals this__7857__auto\
__ G__33921)) (clojure.core/hashCode [this__7854__auto__] (clojure.core/let [hash__7855__auto__ __hash] (if (clojure.core/zero? hash__7855__auto__) (clojure.core/let [h__7856__auto__ (clojure.lang.APer\
sistentMap/mapHash this__7854__auto__)] (set! __hash h__7856__auto__) h__7856__auto__) hash__7855__auto__))) (clojure.core/hasheq [this__7851__auto__] (clojure.core/let [hq__7852__auto__ __hasheq] (if \
(clojure.core/zero? hq__7852__auto__) (clojure.core/let [h__7853__auto__ (clojure.core/int (clojure.core/bit-xor 1742367381 (clojure.lang.APersistentMap/mapHasheq this__7851__auto__)))] (set! __hasheq \
h__7853__auto__) h__7853__auto__) hq__7852__auto__)))) (clojure.core/import net.drilling.x.Tag) (clojure.core/defn ->Tag "Positional factory function for class net.drilling.x.Tag." [dt ident] (new net.\
drilling.x.Tag dt ident)) (clojure.core/defn map->Tag "Factory function for class net.drilling.x.Tag, taking a map of keywords to field values." ([m__7972__auto__] (net.drilling.x.Tag/create (if (cloju\
re.core/instance? clojure.lang.MapEquivalence m__7972__auto__) m__7972__auto__ (clojure.core/into {} m__7972__auto__))))) net.drilling.x.Tag)

Patrick Brown18:03:09

If you notice, I'm calling defdt-record there in place of defdt for brevity. I don't think there is anything to gain from expanding defdt as it calls a few macros.

thheller18:03:00

don't macroexpand all

thheller18:03:06

just what your macro produces

thheller18:03:44

I think CLJS doesn't support the :implements, I don't actually know what that is?

Patrick Brown18:03:54

YUP! You got it right!

Patrick Brown18:03:43

It's an option for clj that allows you to bring the whole namespace with you when you import the type. I can do without it.

Patrick Brown19:03:38

@U05224H0W I'm getting some warnings about use of undeclared var, You're in the know. Is there something I should know? Can I/Should I surpress this?

Use of undeclared Var cljs.core/class
Use of undeclared Var net.drilling.x/clojure

Patrick Brown19:03:36

So aparently there is no function 'class' in cljs.core. Which is surprising. I'm not sure what to make of that.

thheller14:03:22

why would there be? class in JS means something entirely different than in the JVM

thheller14:03:07

the net.drilling.x/clojure is likely referring to some symbol resolving to clojure.lang.something, which also doesn't exist in CLJS.

thheller14:03:16

I see clojure.lang.MapEquivalence