Fork me on GitHub
#hyperfiddle
<
2023-02-28
>
JAtkins01:02:29

Is there a limit to recursive calls? Looks like I caused a stack overflow in the compiler when I tried.

2
Dustin Getz01:02:52

you probably ran out of stack, the compiler is not optimized, increase stack size with the jvm flag in our deps.edn

Dustin Getz01:02:09

what “recursive call” where you doing exactly?

JAtkins01:02:08

I'm looking at a recursive reactive call to render a possibly recursive entity tree

JAtkins01:02:23

recursive, but terminating, that is

JAtkins01:02:35

Something akin to

{:strs-to-render ["...."]
 :recursive-target {:strs-to-render ["...."]}}

(e/defn RenderRecursive 
  [x]
  (dom/div 
   (dom/text (apply str (:strs-to-render x)))
   (when-let [r (:recusive-target x)]
     (RenderRecursive. r))))

Dustin Getz02:02:10

i don’t even think we support recursive calls, you have to use a workaround to recur by binding, it’s linked at the bottom of the readme

👍 2
Dustin Getz02:02:15

btw when i said you may have run out of stack, i meant the electric compiler ran out of stack during the macroexpansion analysis

JAtkins02:02:27

can electric functions return functions? e.g. I've got a fn that returns two bits of dom that I'd like to render in different locations. e.g.

(defonce !a #?(:clj (atom 4)
               :cljs nil))

(e/defn TestReturnsFn
  [text]
  {:a (fn []
        (dom/div
         (dom/text (str ":a TestReturnsFn" text))))
   :b (fn []
        (dom/div
         (dom/text (str ":b TestReturnsFn" text))))})

(e/defn OwningFn
  []
  (let [a (e/watch !a)]
    (e/client
     (dom/div
      (dom/text "btn")
      (ui/button
       (e/fn [] (e/server (e/discard (swap! !a inc))))
       (dom/text "btn"))
      (let [{:keys [a b]} (TestReturnsFn. a)]
        ;;(a)
        ;;(b)
        ;;(b)
        nil
        )
      ))))
------ ERROR -------------------------------------------------------------------
 File: /Users/jarrett/code/jra/electric-datomic-browser/src/user.cljs:8:3
--------------------------------------------------------------------------------
   5 |     hyperfiddle.electric-dom2))
   6 | 
   7 | (def electric-main
   8 |   (hyperfiddle.electric/boot ; Electric macroexpansion - Clojure to signals compiler
---------^----------------------------------------------------------------------
null
Can't set! local var or non-mutable field at line 8 user.cljs
--------------------------------------------------------------------------------
   9 |     (app.datomic-browser/DatomicBrowser.)))
  10 | 
  11 | (defonce reactor nil)
  12 | 
--------------------------------------------------------------------------------
The compiler seems upset though.

2
Geoffrey Gaillard06:02:13

In your example, :a and :b are regular clojure fns. They should be e/fn since you are using electric-dom inside of them. e/fn, just like e/defn, is called with new

Geoffrey Gaillard06:02:09

The compiler error message is not helpful, sorry for that. We'll improve it.

Geoffrey Gaillard11:02:25

The error message now looks like this:

Encountered error when macroexpanding hyperfiddle.electric/boot.
`hyperfiddle.electric-dom2/node` is an Electric var and cannot be bound from a Clojure context.
{:file "user_main.cljc", :line 90, :in [(fn* ([] (dom/div (dom/text (str ":a TestReturnsFn" text)))))]}

👍 2
JAtkins14:02:28

ah, nice. I didn't think about that 👍

Dustin Getz20:02:36

There is a lot there, depending how deep you wanna go'

👍 2
tatut09:02:13

is there a good example of testing electric components? do you unit test a component or do browser testing like cypress?

2
tatut09:02:44

I guess you need a headless browser environment to properly unit test a component

xificurC09:02:48

Our experimental ui4 controls have headless browser tests, e.g. for https://github.com/hyperfiddle/electric/blob/c9c2d8504092d4a034c6f1cb9e85423964e1299c/test/hyperfiddle/electric_ui4_typeahead_test.cljc. These run with every git push. They were written through a cljs repl with a real browser, so you can write/debug the tests incrementally

tatut09:02:14

nice, I’ll check those out

Dustin Getz14:02:10

@U11SJ6Q0K to be clear, are you looking for UI tests? i.e. testing the DOM views?

tatut14:02:29

well examples of any levels of tests

tatut14:02:39

if you create components that mix frontend and backend stuff, then I would expect to want to test the whole component, not just dom

Dustin Getz14:02:07

https://github.com/hyperfiddle/electric/tree/master/src-docs/user/electric shows how to test electric functions, this is an old language tutorial

Dustin Getz14:02:56

here is our https://github.com/hyperfiddle/electric/blob/master/test/hyperfiddle/electric_test.cljc (we have other lower level tests for compiler/runtime which wont be helpful)

Dustin Getz14:02:18

I am not sure if this will help you though

tatut14:02:30

I’ll look into those, thanks

Dustin Getz14:02:07

If I was using Electric to build something like Electric Datascript, i'd expect the tests to be mostly unit tests like this

Dustin Getz14:02:48

If I was using Electric to build a CRUD app, i'd expect the queries to be written in Clojure in the usual way (i.e. this is your problem not an electric problem)

Dustin Getz14:02:24

Your question is thus really about how to test the views of the crud app (the query/dom composition), not the queries in isolation. Do I understand?

tatut14:02:20

there’s usually a testing pyramid, most tests are unit tests, then there are integration tests that test these in unison and finally e2e tests

👀 2
tatut14:02:00

so usually the e2e will be cypress/playwright/etc running against a “real” deployed env, but the middle layer of testing that components work together

tatut14:02:14

so, unit tests are covered with normal clojure.test code

Dustin Getz14:02:48

the middle query/view component integration tests (is this description accurate?) is the part that I dont have a clear answer for you yet — but here's the thing, I have never seen an answer i liked on any stack. What is the best practice for this in a React.js/GraphQL app?

tatut14:02:51

usually there is none, because the frontend and backend is completely separate… I was sort of hoping that with electric there could theoretically be a good solution for this middle level of tests 🙂

Dustin Getz14:02:44

i agree that with the electric primitives we may be able to do better than the status quo

Dustin Getz14:02:47

i just don't know how yet

tatut14:02:03

the middle level I have seen is of the “let’s test the APIs we expose to the frontend work properly”

👍 2
Dustin Getz14:02:46

To some extent, the point of the JVM is to trust that the JVM has unit tests in place for memory management, so that userland doesn't need to write tests for that anymore

Dustin Getz14:02:26

Electric managed network I think is like that, application programmers shouldn't need to test it

Dustin Getz14:02:06

But higher level component tests, like "I made a server streamed paginated grid, use CI to prove that it doesn't crash and that it doesn't leak DOM"

Dustin Getz14:02:41

There is a place for that, and the https://github.com/hyperfiddle/electric/blob/c9c2d8504092d4a034c6f1cb9e85423964e1299c/test/hyperfiddle/electric_ui4_typeahead_test.cljc example Peter linked may be a starting point for developing tests like that. But it's an open question, still working out the idioms

Dustin Getz14:02:07

For example, our unit tests today don't run a jvm/browser system, they are browser-only or jvm-only and any client/server in the test is loopback

Dustin Getz14:02:17

so we'll need to figure that out

tatut14:02:22

that would be great, if there was a good solution to that

tatut14:02:48

but not a deal breaker, usually one needs e2e tests anyway to make sure the final product works in a deployment scenario anyway

👍 2
tatut14:02:32

but, if there were a great way to do the middle layer of tests, I would write more of those and less of cypress tests (as I don’t really enjoy writing cypress tests)

Dustin Getz14:02:04

can i see a snippet of cypress tests that you don't like writing?

tatut14:02:21

can’t show work code, but even the todomvc test suite https://github.com/tatut/cypress-example-todomvc/blob/master/cypress/integration/app_spec.js (that’s not too bad, but still would prefer clojure)

👀 2
tatut14:02:21

you often end up needing to add either classes or some data-cy attributes to your markup to make it more easily testable

👀 2
tatut14:02:47

and then there’s the dom detach stuff, waits for weird async behaviour

Dustin Getz23:02:35

excellent link ty

J13:02:28

Hi guys! A datascript query should be wrapped into a e/wrap ?

2
J13:02:30

I have a query who render some items data. Without e/wrap only partial information are re-render (see the right window) when some informations are updated.

J13:02:00

Now if the query is wrap by e/wrap it seems like all the items are re-render when an update occurs:

J13:02:57

This behaviour is normal?

Geoffrey Gaillard13:02:19

The first video seems to show the right behavior: only what changed is rerendered. e/wrap is required for IO blocking operations. Since datascript is in-memory, and if your query is fast enough to not block the main thread, then e/wrap is not necessary. e/wrap will start by throwing Pending, then will emit the result of its body. In the second video, the list rerenders because of Pending is thrown by e/wrap. To improve your understanding of Pending, a good exercise would be to add (try … (catch Pending …)) around your e/wrapped query, then move this try block higher up in your code, so you can see the difference.

tatut13:02:54

isn’t the previous value in effect while the query is pending? or is that only when pending is caught

J13:02:54

Thanks @U2DART3HA for you explanation. With try/catch , I have the same behaviour. Maybe, I made a mistake. I have something like this:

(e/defn Rooms []
  (e/server
    (let [rooms (try
                  (e/wrap (repo/list-room db/db))
                  (catch Pending _))]
    ...
    (dom/div (dom/props {:class (styles :rooms/room-list-container)})
      (e/server
        (e/for-by :room/id [{...} rooms]
          (e/client ...)))) 

Geoffrey Gaillard14:02:50

(catch Pending _) returns nil, so when Pending is thrown, rooms is nil. Therefore e/for-by will unmount all rows. When e/wrap returns the query result, rooms is now a collection again, so e/for-by will remount all rows.

Geoffrey Gaillard14:02:15

@U11SJ6Q0K In Electric, try is reactive. You can think about reactive try as a conditional: • If there is no Pending being thrown ◦ then run the try branch ◦ else also run the catch branch The main difference between reactive try and reactive if, is that the try branch keeps running when a Pending is thrown. This way when the Pending exception is not thrown anymore, the catch branch is discarded.

J14:02:02

So when Pending is thrown, the list will be re-render on each update?

Dustin Getz15:02:28

@J move the try/catch Pending higher. DOM remains attached during Pending (and any other exception). Exceptions do not unmount DOM.

Dustin Getz15:02:05

> isn’t the previous value in effect while the query is pending? or is that only when pending is caught @U11SJ6Q0K the answer is yes

J15:02:14

I have something like this now:

(e/defn Rooms []
  (e/server
    (try
      (let [rooms (e/wrap (repo/list-room db/db))]
        ...
        (dom/div (dom/props {:class (styles :rooms/room-list-container)})
          (e/server
            (e/for-by :room/id [{...} rooms]
              (e/client ...))
       (catch Pending _)))
But still the same behaviour.

Dustin Getz15:02:04

@UHZPYLPU1 we've just now found another regression related to pending, sorry. Not sure if it impacts what you are seeing or not

J15:02:14

No problem!

Dustin Getz21:02:52

@UHZPYLPU1 the Pending regression is fixed in master

Dustin Getz21:02:39

Here is a demonstration of correct pending behavior, lightly modified from demo-4-chat. • The document.body flashes yellow due to the Pending thrown at L28, and also L16 (try two tabs)

Dustin Getz21:02:51

@UHZPYLPU1 if you're still seeing the lists rebuild, let's please use a common demo as a starting point, like user.demo-4-webview or user.demo-3-system-properties or user.demo-4-chat (whichever fits your situation best)

J06:03:43

Thanks @U09K620SG I will take a look!

J12:03:58

@U09K620SG it works! Thank

👍 1
Dustin Getz21:02:28

The pending regression in master is fixed, also increasing confidence that master hot code reloading is stable