Fork me on GitHub
#clojurescript
<
2020-04-29
>
Spaceman05:04:59

To test my app's routes, I'm using the reitit frontend, push-state function like so:

(rfe/push-state :home)
And my routes are:
(def router
  (reitit/router
   [["/" {:name        :home
          :view        #'home
          :controllers [{:start (fn [_] (rf/dispatch [:page/init-home]))}]}]
    
    ]))
But I get the error:
No protocol method Router.match-by-name defined for type null: 
Why is that and what am I doing wrong?

Raziyeh Mohajer06:04:51

Hello everyone, I have a backend app with Clojure and a frontend app with re-frame. And from my front, I want to request my backend for an excel file. I use http-xhrio for sending requests. The request Accept should be application/octet-stream but I didn't find any suitable response-format in ajax.core. Is there a good way of receiving files in re-frame?

Doni Rubiagatra07:04:56

hello raziyeh you can use this kind of response format

Doni Rubiagatra07:04:12

:response-format {:content-type "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                                    :description "excel"
                                    :read protocol/-body
                                    :type :arraybuffer}

Doni Rubiagatra07:04:55

and on success you can create dialog window for download

Doni Rubiagatra07:04:41

but don't forget make it blob first

Raziyeh Mohajer07:04:14

Thanks Doni Rubiagatra, what is protocol/-body ? and from what namespace I should require it?

Doni Rubiagatra07:04:15

yes you should require it

Doni Rubiagatra07:04:17

[ajax.protocols :as protocol]

Raziyeh Mohajer07:04:50

And I should implement the protocol record myself or Is there an implementation for it?

Doni Rubiagatra07:04:36

no just call it by protocol/-body

Raziyeh Mohajer07:04:30

by making it blob, you mean on-success of event handler I should change it?

Doni Rubiagatra07:04:28

maybe this will get you the idea

Raziyeh Mohajer07:04:06

Aha Thank you very much 🙂

Doni Rubiagatra07:04:01

you are welcome 🙂

ingesol14:04:25

So I believe https://github.com/Olical/cljs-test-runner is broken with the :bundle target. It expects to be able to use the :output-to file, but that file lacks a webpack build step. What would be a proper solution, except waiting for a new release of the runner? Have webpack do some renaming trickery to get the right file at the right spot?

dnolen15:04:41

@ingesol I would say this is just fundamentally a bug

dnolen15:04:06

well sorry not a bug

dnolen15:04:10

but a very important missing feature

dnolen15:04:29

not being able to choose the file that the test runner should use

dnolen15:04:07

seems like a very simple thing to fix though

dnolen15:04:34

I wouldn't hack around it myself

dnolen15:04:31

git deps make it easy to "fork and continue"

👍 4
dnolen15:04:38

while you wait for the PR to get merged

lilactown15:04:03

anyone have experience setting up testing of a CLJS lib w/ github actions? example repos appreciated

thheller15:04:36

if your tests run in node then its easy

thheller15:04:41

if you need a browser then no clue 😛

lilactown15:04:40

thanks, this helps

lilactown15:04:06

I think they should run in in Node.js

lilactown15:04:35

I have tests in both CLJ and CLJS, I'm not sure if that complicates things further

Spaceman15:04:54

How do I make sure that all the functions in a test are run sequentially?

(deftest mytest
    (f1)
    (f2)
)
These functions cause side effects so I want them to run sequentially, but how do I do that? I tried go block as follows:
(deftest mytest
 (go
    (let [f1- (<p! (f1))
          f2- (<p! (f2))
         ]
        (prn "done")
)
))
But this doesn't seem to work because done isn't printed on running the test. Also, (f1) returns nil so I'm not sure I can even use <p! in that case. How do I run these functions sequentially?

henryw37415:04:34

f1 will have to return a promise which will mean all of the stuff it started has finished - even if you don't care about it's value in the test.

Spaceman21:04:50

does that mean for all f1, f2 ... I have to wrap them with (.Promise)? Is there an elegant way to do this in bulk for each function in the sequence? I am using the re-frame test library though that doesn't really seem to handle checking using f2 if a component has been mounted using f1.

Spaceman21:04:04

Basically what I'm trying to mount a component using f1 and then checking whether it was mounted using f2, using the react-testing-library and getByText method.

henryw37419:05:33

I don't know if there's anything more elegant. The testing lib surely has examples in js

Spaceman20:05:29

The Async waitFor in the library seems broken.

henryw37415:04:41

.then(f2) after. fyi I wrote a helper around the Clojurescript async testing to deal with timeout and exception http://widdindustries.com/cljs-async-tests/

Adam Helins16:04:46

Is there a genuine equivalent to alter-var-root? set!is often mentioned but it works on a var symbol, not a var

lilactown16:04:14

no, CLJS doesn’t actually use vars. you can get a Var type but changing it at runtime does not actually effect the global environment, it’s used just to get some compile-time metadata

Adam Helins16:04:03

@lilactown My CLJ code accepts a Var and adds metadata to the root (knowing it is always an IMeta). I am not quite sure how to do it from CLJS, if that is possible because of advanced compilation

dnolen16:04:25

not possible in CLJS

dnolen16:04:31

var stuff in Clojure is not portable

Adam Helins16:04:37

Thanks! Unfortunately the more I dive into CLJS, the more I hear "not possible". Maybe the stuff I do is too esoteric...

lilactown16:04:41

what are you trying to do? there might be a way to split the difference with some compile-time macros and runtime code

4
Adam Helins16:04:35

Yes I can solve my problem using a macro but then I would need to define my own defnand I find it always ugly to do so, whereas my current CLJ solution is barely a few lines and just works

lilactown16:04:33

there are people here who would listen to your high-level problem and might be able to guide you toward a better solution 🙂

lilactown16:04:32

just inferring here, it sounds like you’re doing something like:

(defn foo ,,,)

(set-some-meta! foo)
you might be able to do something similar by having set-some-meta! operate at macrotime. then you can pull out this info using another macro like:
(get-some-meta foo)
or at runtime like (meta #'foo)

Adam Helins16:04:06

I am gearing towards something like this indeed. But no need for a macro for pulling out, is there?

lilactown16:04:29

depends, constructing a var has a runtime cost - it’s faster to find the specific meta on the symbol at macrotime and emit it

lilactown16:04:48

either way, the name of the “var” that you’re trying to get the meta from must be statically knowable

👍 4
ingesol16:04:51

@dnolen Sounds reasonable, should be easy enough to work around this by patching the lib.

dnolen16:04:11

@adam678 it's not that what you're doing is esoteric

dnolen16:04:29

it's that you're doing things that ClojureScript intentionally hasn't supported since day one

dnolen16:04:12

none of the runtime var metaprogramming stuff in Clojure works - if you want to write portable stuff - you have to do it a different way

Vishal Gautam16:04:20

Whats the best approach to test ClojureScript view functions in a web app. I tried looking online but couldnt find any good resources.

souenzzo19:04:24

Checkout figwheel cards and #workspaces

aisamu12:04:55

("figwheel cards" are probably devcards)

👍 4
Vishal Gautam13:04:38

I am going to try playing wth devcards

Vishal Gautam13:04:48

thank you guys!

dnolen17:04:02

some of the var meta programming things can be accomplished by other means

dnolen17:04:35

cljs.test and cljs.spec.alpha demonstrate that non-trivial things can be done with reasonably similar APIs

dnolen17:04:52

without any var meta programming at all

dnolen17:04:03

or extremely restricted variant of it

ingesol17:04:32

@vishal.gautam This one is specific to re-frame, but pretty good in general https://github.com/day8/re-frame/blob/master/docs/Testing.md

Vishal Gautam17:04:40

Yeah I went through that article. It talks about testing subscription layers and event handler functions. But talks very little about testing view functions

Adam Helins17:04:17

@dnolen Fair enough, thanks, one simply has to be more creative!

lilactown17:04:53

I have enjoyed using react-testing-library if you’re using a react wrapper (e.g. reagent)

Vishal Gautam17:04:21

@lilactown do you have a sample code I can reference at

lilactown17:04:44

react-testing-library has a render function that you pass a React element to. so something like:

(rtl/render (reagent.core/as-element [:div {:data-testid "my-div"} "Hello, world!"]))
the render function returns a result object with a bunch of methods, that you call to test your view:
(deftest my-div-renders
  (let [result (rtl/render ,,,)]
    (cljs.test/is (.getByTestId result "my-div"))))

lilactown17:04:53

the react-testing-library has pretty good docs

Vishal Gautam17:04:32

Alright, thank you, I will take a look

Vishal Gautam17:04:47

Made it work using karma and react-testing-library

lilactown17:04:26

not really, but it’s fairly straight forward using JS interop

Adam Helins17:04:09

(@dnolen Thanks for everything you've done for CLJS by the way, I don't mean to sound like I'm complaining about it 😉 )

dnolen17:04:48

@adam678 didn't take it that way, just wanted to clarify. The var thing is intentional because of advanced compilation & there's nothing wrong w/ var-meta programming in Clojure. Just that if you're going to port that stuff you must do something quite different for ClojureScript.

dnolen17:04:55

I do generally think twice about var meta-programming if I'm doing library work that's meant to portable

dnolen17:04:20

it's considerably easier to do in Clojure - where in ClojureScript it's significantly more challenging to come up with solutions that don't create more problems

👍 4
Michael J Dorian22:04:02

Apologies for the weird premise, but I have a collection of things that I can't count. I can only run a variant of (map) on them, and I need to know how many there are. I would normally use agents or refs to update a reference each time the function in map runs, and then await it (in the case of agents). I have the code written right not with an atom, basically just treating the atom like an agent, and it works. As far as I can tell, the updates all get applied before I deref the atom 100% of the time, but it makes me nervous none the less. Is there any sort of drop in replacement I can use for synchronous state?

noisesmith22:04:03

does the variant of map allow a callback at the end of the input?

noisesmith22:04:22

if so, you could thread a promise or a chan write

noisesmith22:04:05

also consider that the js env is only opt-in concurrent, so if your function doing the "variant of map" doesn't opt in, it won't be interrupted

Michael J Dorian22:04:59

It doesn't have callback support. I'm pretty sure the map (rewrite-clj's map-vals) doesn't do anything concurrent so if I could depend on it to work, maybe that's the best bet. I could even write a variant of the function for non-js that uses agents

dpsutton22:04:06

can you make a seq out of the collection with a special sigil at the end to signal that you are done?

noisesmith22:04:46

I checked the context - it's a zipper not a collection

noisesmith22:04:10

but zippers are immutable, so you can safely extract the node you are at and do normal collection ops on it

noisesmith22:04:53

of course you could also make assumptions about the zipper implementation (clojure zipper uses immutable data structures) but it's safer to inflate it first

Michael J Dorian22:04:22

I don't know how to collect them, though. The only rewrite-clj function that differentiates between map values and other nodes is this map one

Michael J Dorian22:04:02

Not coworkers @U11BV7MTK simple_smile I'm on a side project

dpsutton22:04:31

ah ok. seemed like some shared context i was missing 🙂

noisesmith22:04:05

I went and looked at what rewrite-clj was doing, it's using a zipper, these are defined by clojure.core

noisesmith22:04:22

sometimes I get very curious

Michael J Dorian22:04:10

Hahaha, well my problem was weird, too. I wouldn't have been able to come up with such a good reason to not be able to count something if I tried

noisesmith22:04:07

you can just call (count (zip/node x)) if they are using zippers that are compatible with clojure.zip

noisesmith22:04:12

worth a try at least :D

Michael J Dorian22:04:08

It runs! So it's a real zipper, I would just need a way to filter out all the nodes based on their type. I wish there was a (z/map-val? ) function

noisesmith22:04:36

(comp map? z/node) :D

noisesmith22:04:56

z/node is relatively cheap, especially if you are further down the tree

noisesmith22:04:51

zippers are on my "need a week off work, five gallons of caffeine, and a quiet space" list

Michael J Dorian22:04:00

ah, when I say map value I mean {:not-this "only this"}, map? is how I got here

Michael J Dorian22:04:33

LOL, maybe I should look into them more, I thought I was dealing with something specific to this lib

noisesmith22:04:45

oh - map-val? would likely be something like #(and (map-entry? (z/node (z/up %))) ...)

noisesmith22:04:14

where the second clause of and also checks that you are on the right-side of the parent rather than the left (don't know that one off the top of my head)

Michael J Dorian22:04:59

That's the trick though, because now I'm counting forms and throwing out comments and whitespace

Michael J Dorian22:04:29

Which may be the best answer 🤷 I just wanted an agent!

Michael J Dorian22:04:51

Thank you so much! I'm going to commit the reader conditional version where I can at least be confident that atoms are only used in a single threaded environment, and then read those!

Spaceman22:04:13

So I have a promise in a test like so:

(deftest login-test
  (day8.re-frame.test/run-test-async
   (testing "login test"
     (async done
            (go
              (let [_ (<p! (js/Promise. (see/landing!)))
                   
                    ]
                (prn "done")
                )
              )
            (done)
            )
     )
   )
  )
And running this test just hangs. What do you think is causing this?

Spaceman22:04:15

Oh yeah, I'm also getting this error on evaluating just the js/Promise part, but have no clue what it means:

TypeError: Promise resolver #<cmp> is not a function
TypeError: Promise resolver #<cmp> is not a function
    at new Promise (<anonymous>)
    at eval (eval at shadow$cljs$devtools$client$browser$global_eval (), <anonymous>:1:2)
    at eval (<anonymous>)
    at Object.shadow$cljs$devtools$client$browser$global_eval [as global_eval] ()
    at eval ()
    at Object.shadow$cljs$devtools$client$env$repl_call [as repl_call] ()
    at Object.shadow$cljs$devtools$client$browser$repl_invoke [as repl_invoke] ()
    at shadow$cljs$devtools$client$browser$handle_message ()
    at eval ()
    at Object.shadow$cljs$devtools$client$env$process_next_BANG_ [as process_next_BANG_] ()

verma22:04:45

@pshar10 js/Promise. will accept a function which accepts two other functions to be called for resolve/reject cases. is that what (see/landing!) returning?

Spaceman22:04:08

that function is supposed to and returns nil

verma22:04:20

oof, what are you trying to do?

Spaceman22:04:47

I'm trying to mount a component with (see/landing!) on the dom. And after that check whether it was mounted

Spaceman22:04:22

(deftest-sync login-test (day8.re-frame.test/run-test-async (testing "login test" (async done (go (let [_ (<p! (js/Promise. (see/landing!))) click-login-btn (<p! (js/Promise. (interact/click-login-btn)) ) ] (prn "done") ) (done) ) ) ) ) )

verma22:04:36

so you want to wait for the see/landing! to finish doing its thing?

Spaceman22:04:46

not check but click a button on the mounted component

verma22:04:31

I would think in that case, see/landing! would need to indicate that somehow (that its done), either by returning a async/chan or a Promise.

Spaceman22:04:02

also evaluating the promise part gives me this:

TypeError: Promise resolver #<cmp> is not a function
    at new Promise (<anonymous>)
    at eval (eval at shadow$cljs$devtools$client$browser$global_eval (), <anonymous>:1:118)
    at eval (eval at shadow$cljs$devtools$client$browser$global_eval (), <anonymous>:7:3)
    at eval (<anonymous>)
    at Object.shadow$cljs$devtools$client$browser$global_eval [as global_eval] ()
    at eval ()
    at Object.shadow$cljs$devtools$client$env$repl_call [as repl_call] ()
    at Object.shadow$cljs$devtools$client$browser$repl_invoke [as repl_invoke] ()
    at shadow$cljs$devtools$client$browser$handle_message ()
    at eval ()

Spaceman22:04:27

that was my intention of returning a promise by wrapping it with (js/Promise.)

verma22:04:30

yeah because you're passing in nil where its expecting the executor function

Spaceman22:04:58

maybe forget what I have here. From scratch, how would you approach this problem?

verma22:04:23

(js/Promise. (fn [resolve reject] (somehow-wait-for (see/landing!)) (resolve)))

Spaceman22:04:05

this is how the react-testing-library does it: https://testing-library.com/docs/react-testing-library/example-intro. But it uses jest, which I can't install

verma22:04:11

depending on what see/landing! is doing, it might vary

Spaceman22:04:15

what is some-how-wait-for?

Spaceman22:04:36

and reject and resolve are specious here.

verma22:04:48

some kind of wait mechanism

Spaceman22:04:00

see landing is

Spaceman22:04:19

(defn landing! [] (start-router!) (ajax/load-interceptors!) (mount-components)) )

verma22:04:32

are these all "sync" calls?

Spaceman22:04:29

Let's assume they are.

verma22:04:01

you can also directly wrap with Promise.resolve

verma22:04:23

but if they are sync why are you wrapping them in a promise, sorry I am trying to understand the problem

Spaceman22:04:16

Here's the problem. I'm testing user interaction, and I first mount the component on the screen using see/landing!, and then make the user click a button on that component, using interact/click-login-btn

Spaceman22:04:40

so they must run sequentially.

Spaceman22:04:44

How do I do that?

verma22:04:18

just directly call (see/landing!) since its sync, when the call returns things are in place

verma22:04:33

promise/async are to wait for async things, since your stuff is sync, just call one after the other

Spaceman22:04:36

wrap it in async or not?

verma22:04:46

you wouldn't need to I would think.

Spaceman22:04:03

then it's async, because it didn't work that way

verma22:04:09

then (see/landing!) needs to somehow indicate when its done (by returning a promise) .. or you if want to wait for things to settle try (<! (async/timeout 10000)) ..

verma22:04:38

terrible advice though, I would suggest modeling see/landing! differently to allow for proper async initialization

Spaceman22:04:17

what's a different way to model it? Maybe using the built-in render function from react-testing-library?

verma22:04:26

(defn landing! []
  (go 
     (<! (start-router!))
     (<! (ajax/load-interceptors!))
     (<! (mount-components))))

verma22:04:48

I am not aware of implementation details, but async call other async sort of

noisesmith22:04:51

in that case, you can use the chan landing returns (go returns a channel)

noisesmith22:04:33

To be more concrete, go returns a channel, the channel provides the value of the last statement in the block, when that statement returns. You can make your own go that uses that channel, and does other things after reading, or use take! and put your code into the optional callback arg that isn't called until the channel provides a value

Spaceman23:04:05

would you please give an example @noisesmith?

noisesmith23:04:38

(let [c (see/landing!)] (take! c (fn [] (my-code-goes-here))))

Spaceman23:04:00

see/landing! returns nil though

Spaceman23:04:06

not a channel

noisesmith23:04:06

or (go (<! (see/landing)) (my-code-goes-here))

noisesmith23:04:22

then the code that verma shared above isn't the code for landing

noisesmith23:04:40

if so, I apologize for the confusion

noisesmith23:04:16

oh! - I misread, @verma was suggesting an implementation, not sharing what it was facepalm

noisesmith23:04:52

@pshar10 anyway, there's no simple solution to this stuff in js, people use async to deal with the vm only having one thread, but async introduces complexity and thus bugs

noisesmith23:04:30

learning to use promises and/or core.async makes things harder at the start but will be better in the long term

tekacs23:04:34

I'm having quite some trouble following the ClojureScript webpack guide somehow. Would anyone know why ClojureScript would appear to emit this in 'main.js', when set to emit with REPL?

var CLOSURE_UNCOMPILED_DEFINES = {"clojure.browser.repl.PORT":9000,"cljs.core._STAR_target_STAR_":"bundle","clojure.browser.repl.HOST":"localhost","goog.json.USE_NATIVE_JSON":true};
var CLOSURE_NO_DEPS = true;
document.write('<script src="out/goog/base.js"></script>');
document.write('<script src="out/goog/deps.js"></script>');
/cljs_deps.js"></script>');
document.write('<script src="out/brepl_deps.js"></script>');
document.write('<script>goog.require("clojure.browser.repl.preload");</script>');
note the line /cljs_deps.js"></script>'); which is invalid When I ignored this error and just built a basic core.cljs using Helix, I ran into a separate issue, which I also had trouble diagnosing: https://gist.github.com/tekacs/a46476615782a45f29005d15bc0f27b7 My config is also present in this latter gist.

lilactown23:04:14

hey tekacs. I'm guessing that the master branch of helix won't work with the webpack guide

lilactown23:04:27

there's another branch, called fix-figwheel, that might work better for you

lilactown23:04:56

it doesn't seem directly related to the issue you're dealing with now, but once you get past it, try the fix-figwheel branch using git deps

dnolen23:04:33

@tekacs seems very strange, did you try to erase your :output-dir and start fresh?

tekacs23:04:49

trying now

tekacs23:04:56

same result 😞

tekacs23:04:23

let me bundle this up and push it

dnolen23:04:06

k can't take a look right now, but maybe someone else can check it out - if it seems reproducible then open an issue and of course I'll look into it

tekacs23:04:27

of course -- I'll put it up where folks can take a look and keep trying to debug myself

tekacs23:04:33

thanks for chiming in

dnolen23:04:36

@tekacs I will say what you're seeing seems almost impossible

dnolen23:04:49

make sure you don't have multiple compiler process running accidentally

dnolen23:04:57

like a forgotten one in a different terminal

tekacs23:04:52

checking out on another computer

tekacs23:04:07

it's here: https://github.com/tekacs/cljs-issue-reproduction-1 I'll pull it on another machine and test

tekacs23:04:07

nope -- I checked it out on a completely different computer from scratch using the repo above and saw the same result... 😓

tekacs23:04:32

I even used curl to pull /out/main.js to be sure that my browser wasn't causing the issue

tekacs23:04:18

so it should be possible to reproduce by checking out the above repo and running: $ yarn && yarn start to start the watcher and $ curl to see the broken output I can poke around to see if I can reduce the example further, but there's really not much happening in that repo.