fulcro

sheluchin 2025-06-06T18:14:07.423749Z

Does run-events! support event data?

(defn H-initialize [_env event-payload]
  (println :H-initialize)
  (println event-payload)
  [(ops/assign :foo (get-in event-payload
                            [:_event :data :foo]))])

(deftest demonstrate-correct-event-data-testing
  (testing "A focused example of how to test a handler that updates the data model"
    (let [chart-def (statechart {}
                      (data-model {:expr {:foo nil}})
                      (state {:id :state/initial}
                        (transition {:event :event/initializing
                                     :target :state/initialized}
                          (script {:expr H-initialize})))
                      (state {:id :state/initialized}))
          config {:statechart chart-def}
          overrides {}
          env (testing/new-testing-env config overrides)]
      (println 800000)
      (testing/start! env)
      (testing/in? env :state/initial)

      (testing/run-events! env {:event :event/initializing
                                :data {:foo :bar}})
      (testing/in? env :state/initialized)
      (is (= (testing/data env) {:foo :bar})))))

; FAIL in (demonstrate-correct-event-data-testing) (NO_SOURCE_FILE:33)
; A focused example of how to test a handler that updates the data model
; expected: (= (testing/data env) {:foo :bar})
;   actual: (not (= {:foo nil, :_event nil} {:foo :bar}))
; -  {:foo nil, :_event nil}
; +  {:foo :bar}

sheluchin 2025-06-06T18:15:37.654969Z

It looks like the script doesn't get hit at all. Am I missing something?

michaelwhitford 2025-06-06T18:26:16.673889Z

You mean this one in :state/initial?

(script {:expr H-initialize}

sheluchin 2025-06-06T18:27:03.546069Z

yep

michaelwhitford 2025-06-06T18:29:09.945059Z

I am trying to find the post where tony mentioned a transition can execute, give me a few.

sheluchin 2025-06-06T18:34:07.581419Z

Ah, maybe you mean this one? https://clojurians.slack.com/archives/C68M60S4F/p1733057517439029?thread_ts=1732929918.907499&cid=C68M60S4F it links to a test in the source where it uses simple/send! to send an event with data inside a test.

michaelwhitford 2025-06-06T18:45:25.653639Z

I think your transition looks right to me, not sure why run-events! is not running it. I just searched my own statecharts and I am not using a single transition with a script, all my executable content is in on-entry and on-exit states.

michaelwhitford 2025-06-06T18:46:27.546719Z

I also use simple for all my stuff so I use simple/send! for events.

sheluchin 2025-06-06T18:46:46.052789Z

I can try that to see if it'll work with testing utils. Not a huge difference if it does work.

tony.kay 2025-06-06T19:01:27.747769Z

You’re missing something

tony.kay 2025-06-06T19:01:35.447119Z

The testing environment stubs out everything

tony.kay 2025-06-06T19:03:09.287869Z

https://fulcrologic.github.io/statecharts/#_testing

tony.kay 2025-06-06T19:03:40.601819Z

See the comments about mocking. You specify a map of functions and what they should give as values (let [env (testing/new-testing-env (config) {is-tuesday? true})]

sheluchin 2025-06-06T19:05:34.865649Z

So it's not patching just certain parts of your statechart, you have to provide definitions of all the expressions in your chart and use {a a} sorta thing where you don't want a mock?

tony.kay 2025-06-06T19:16:23.513129Z

at the moment yes, but feel free to copy out the current utils and change how they work.

sheluchin 2025-06-06T19:17:07.184089Z

Still not sure what I'm missing here...

(defn H-initialize [_env event-payload]
  (println :H-initialize)
  ; (println event-payload)
  [(ops/assign :foo :xx)])

(defn my-cond [_ _] 
  (println :my-cond)
  true)

(deftest demonstrate-correct-event-data-testing
  (testing "A focused example of how to test a handler that updates the data model"
    (let [chart-def (statechart {}
                      (data-model {:expr {:foo nil}})
                      (state {:id :state/initial}
                        (transition {:event :event/initializing
                                     :target :state/initialized}
                          (script
                            {:expr H-initialize})))
                      (state {:id :state/initialized}))
          config {:statechart chart-def}
          overrides {H-initialize (fn [env & _]
                                    (println 99999)
                                    [(ops/assign :foo 999)])}
          env (testing/new-testing-env config overrides)]
      (println 800000)
      (testing/start! env)
      (is (testing/in? env :state/initial) "Not in :state/initial")

      (testing/run-events! env {:event :event/initializing
                                :data {:foo :bar}})
      (is (testing/in? env :state/initialized)))))

tony.kay 2025-06-06T19:17:14.541499Z

I agree it’s not the best design…in my original thinking what I was thinking was that you’d start the chart in some specific spot, say what those expressions would do, and then run it to show what would happen.

sheluchin 2025-06-06T19:17:39.819659Z

it never prints 99999 or gets into :state/initialized

tony.kay 2025-06-06T19:17:57.337049Z

That way you could protect your expectations in tests without having to deal with the side effects/setup of whatever was behind the scenes

tony.kay 2025-06-06T19:20:23.352149Z

Hm. I don’t see the problem…

tony.kay 2025-06-06T19:20:30.778629Z

as in I’m not sure why it doesn’t

sheluchin 2025-06-06T19:20:54.020509Z

Swapping the run-events! call with (testing/run-events! env :event/initializing) fixes the test, but then I can't test the data

sheluchin 2025-06-06T19:29:18.577079Z

facepalm :event -> :name is the fix:

(testing/run-events! env {:name :event/initializing
                                :data {:foo :bar}})

sheluchin 2025-06-06T19:29:56.656699Z

I find these wrong key errors always consume so much time when I make them.

sheluchin 2025-06-06T19:33:35.554799Z

new-event shows it clearly:

(>defn new-event
  "Generate a new event containing `data`. It is recommended that `data` be
   easily serializable (plain EDN without code) if you wish to use it
   in a distributed or durable environment.

   If using a map:

   `name` - the event name
   `data` - Extra data to send with the event
   `type` - :internal, :external, or :platform. Defaults to :external
but I was looking at usages of simple/send! which usually take :event kw like:
(simple/send! env {:target :main/child
                     :event  :event/exit})

tony.kay 2025-06-06T19:51:29.663869Z

Should probably add guardrails to those, and then turn it on 😄

tony.kay 2025-06-06T19:51:44.757019Z

does run-events! have GR?

tony.kay 2025-06-06T19:51:58.170069Z

that would have told you right away you were doing it wrong

sheluchin 2025-06-06T19:52:21.839619Z

Nope, it doesn't. I've been meaning to ask you, do you dev with Guardrails on most of the time?

tony.kay 2025-06-06T19:53:39.802869Z

Lately I haven’t been writing much, but yes, I try to write most things with it. I’ve added optimizations that make that work out well, and it is definitely part of my testing process that I feel is critical to doing a good job.

tony.kay 2025-06-06T19:54:15.329469Z

These days I use it with Malli though instead of spec

tony.kay 2025-06-06T19:54:45.353049Z

In dev mode I have GR warn but not throw, but in test mode I have GR throw to make tests fail

sheluchin 2025-06-06T19:54:55.892879Z

I turn it off when doing a lot of processing because of the performance cost, but with the new-ish rate limiting settings I can probably try turning it on more.

tony.kay 2025-06-06T19:55:05.755769Z

not just rate limiting

tony.kay 2025-06-06T19:55:13.660319Z

You can filter out entire nses

sheluchin 2025-06-06T19:55:18.275779Z

Why Malli over Spec?

tony.kay 2025-06-06T19:55:41.537929Z

and there is support for libraries to list nses where it should be off as well…which statecharts leverages

tony.kay 2025-06-06T19:57:17.686959Z

Malli over spec: 1. Malli is clearer 2. Malli can be faster (I still need to optmize GR for that). 3. Malli specs can be just data, and the integration of things like custom error messages is easier

tony.kay 2025-06-06T19:57:23.227519Z

probably more…but those come to mind

tony.kay 2025-06-06T19:58:58.437249Z

https://github.com/fulcrologic/guardrails?tab=readme-ov-file#new-in-12 The dynamic exclusions let’s you just turn off checking on swaths of code you know are not needed checks. E.g. the internals of statecharts need checks when I’m working on the lib, but there should be no way for user code to cause internals to fail those checks.

tony.kay 2025-06-06T20:00:27.838379Z

So the library exclusions let the lib put things at the “top” of the classpath that can be found on load where they can auto-exclude things…meaning that you can write libraries that don’t infect projects with performance problems.

tony.kay 2025-06-06T20:00:39.335019Z

THe throttling also helps, but I don’t recommend it when testing

sheluchin 2025-06-06T20:06:43.540919Z

Alright I guess this is the motivation I need to get back on the Guardrails diet. Thanks for your time helping me sort this one out guys.