ReactTestUtils.Simulate.mouseDown - reactjs

I'm trying to use the React Simulate function to simulate a mouseDown event for testing.
(defn mouse-down [node]
((.-mouseDown(.-Simulate ReactTestUtils) node (clj->js {:button 0})))
js translation:
ReactTestUtils.Simulate.mouseDown(node, {button: 0})
Nothing I've tried has resulted in an invocation of the mousedown listener--but when the listener is there when i try it in the browser where it works. It's just in the simulation.
What am I missing?

There are a couple mistakes in the Syntax here and your parens don't match. Generally it becomes easier if you reorganize the code a bit and -> can help there. As mentioned in the comment you want to use .mouseDown instead of .-mouseDown. I opted to use #js instead of clj->js since that is more optimal for static js objects such as this.
(defn mouse-down [node]
(-> (.-Simulate ReactTestUtils)
(.mouseDown node #js {:button 0})))
You can also make this a little more readable depending on where ReactTestUtils is coming from. I'm assuming its from the react-dom package which you just required.
;; in your ns
(:require ["react-dom/test-utils" :as ReactTestUtils])
;; which would allow
(defn mouse-down [node]
(ReactTestUtils/Simulate.mouseDown node #js {:button 0}))
;; or if you are on the latest CLJS version
(:require ["react-dom/test-utils$Simulate" :as sim])
;; and then
(defn mouse-down [node]
(sim/mouseDown node #js {:button 0}))

Related

how to rerender reagent ui with react 18 and shadow-cljs reload?

how to rerender the whole reagent tree when we save file and shadow-cljs reloads?
react 18 has new createRoot api
and even before - if nothing changed calling render has no effect
With react v18, you need to create the root node only once. After this, you can call the .render() function from it to (re-)render your application.
Also, you need to configure a function / behavior to tell shadow-cljs what it should do on during reload.
Here is a full example, taken from here https://github.com/schnaq/cljs-reagent-template
(ns playground
(:require ["react-dom/client" :refer [createRoot]]
[goog.dom :as gdom]
[reagent.core :as r]))
(defn- main []
[:main.container.mx-auto
[:h1 "Welcome to your app"]])
;; -----------------------------------------------------------------------------
(defonce root (createRoot (gdom/getElement "app")))
(defn init
[]
(.render root (r/as-element [main])))
(defn ^:dev/after-load re-render
[]
;; The `:dev/after-load` metadata causes this function to be called
;; after shadow-cljs hot-reloads code.
;; This function is called implicitly by its annotation.
(init))
shadow-cljs is configured to call the init-function from playground/init.
;; shadow-cljs.edn
{...
:builds {:frontend {:modules {:main {:init-fn playground/init}}}}}
https://github.com/move-me-to-ipfs-shipyard/Fennec/blob/bbfb566211041dd57b419ffd95f642026bb989a8/src/Fennec/ui.cljs#L263
(:require
["react-dom/client" :as Pacha.dom.client]
[reagent.core :as Kuzco.core])
; seed.cljs
(defonce root {:dom-rootA (atom (Pacha.dom.client/createRoot
(.getElementById js/document "ui")))})
; ui.cljs
(:require [Fennec.seed :refer [root]])
(defn reload
[]
(when-let [dom-root #(:dom-rootA root)]
(.unmount dom-root)
(let [new-dom-root (Pacha.dom.client/createRoot
(.getElementById js/document "ui"))]
(reset! (:Pacha-dom-rootA root) new-dom-root)
(.render #(:Pacha-dom-rootA root)
(Kuzco.core/as-element [rc-current-page])))))

Problem with Autocomplete (Material UI + React + Reagent/ClojureScript)

I have a problem using the Material UI's Autocomplete with Reagent (ClojureScript). The element renders fine, but when I try to click on it, I get the following exceptions:
Uncaught TypeError: Cannot read property 'focus' of null
at handleClick (useAutocomplete.js:938)
at HTMLUnknownElement.callCallback (react-dom.development.js:189)
at Object.invokeGuardedCallbackImpl (react-dom.development.js:238)
at invokeGuardedCallback (react-dom.development.js:293)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:307)
at executeDispatch (react-dom.development.js:390)
at executeDispatchesAndReleaseTopLevel (react-dom.development.js:412)
at forEachAccumulated (react-dom.development.js:3260)
at runEventsInBatch (react-dom.development.js:3305)
at handleTopLevel (react-dom.development.js:3515)
useAutocomplete.js:322 Uncaught TypeError: Cannot read property 'removeAttribute' of null
at eval (useAutocomplete.js:322)
at eval (useEventCallback.js:26)
at eval (useAutocomplete.js:433)
at eval (useEventCallback.js:26)
at eval (useAutocomplete.js:463)
at eval (useAutocomplete.js:528)
at commitHookEffectListMount (react-dom.development.js:19765)
at commitPassiveHookEffects (react-dom.development.js:19803)
at HTMLUnknownElement.callCallback (react-dom.development.js:189)
at Object.invokeGuardedCallbackImpl (react-dom.development.js:238)
Breaking in JS debugger, I see that inputRef.current is null (which is what focus and removeAttribute are called on. (Oddly enough, the only place where inputRef is set in the file is by calling useRef(null), which is what results in inputRef.current being null.)
In my code, I define the Autocomplete field as follows:
(ns my-product-redacted.views.atoms
(:require [reagent.core :as r]
["#material-ui/lab/Autocomplete" :default Autocomplete]
;; other requires
))
(def autocomplete-field (r/adapt-react-class Autocomplete))
Then, in a React component, it is used as follows:
[a/autocomplete-field {:render-input (fn [js-params]
(let [clj-params (js->clj js-params)
params {:label label
:width width
:select select?
:Input-label-props {:shrink true}
:Select-props {:native true}}
all-params (into clj-params params)]
(js/console.log (clj->js all-params))
(r/as-element [a/text-field all-params])))
:options (when select? (cons {:value "" :label ""} options))
:get-option-label (fn [option] (or (get (js->clj option) "label") ""))
:default-value (when (not select?) value-override)
:value (when select? value)
:disabled disabled?
:on-focus #(re-frame/dispatch [::forms/on-focus path])
:on-blur #(re-frame/dispatch [::forms/on-blur path])
:on-change #(re-frame/dispatch (conj on-change (-> % .-target .-value)))})]
(Here, a/text-field is also defined in the same namespace as a/autocomplete-field and in a similar way.)
The JS console log (from the (js/console.log (clj->js params)) call) shows that inputProps.ref.current is set to null. However, InputProps.ref is not null. Even so, I tried to manually associate the same function as is passed with InputProps.ref to inputProps.ref.current, but that made no difference.
I have also tried the workaround suggested in https://github.com/mui-org/material-ui/issues/21245 (although that issue is with the Gestalt library, not with Reagent, it suggests that there may be a problem with ref forwarding). But wrapping the text-field into a div with the ref taken from InputProps.ref also made no difference.
Any suggestions?
Calling js->clj on the render-input js params breaks them.
I put a quick demo of the material UI autocomplete component in my demo repository here.
But it's mainly taken from the official reagent docs/examples here:
(defn autocomplete-example []
[:> mui/Grid
{:item true}
[:> Autocomplete {:options ["foo" "bar" "foobar"]
:style {:width 300}
;; Note that the function parameter is a JS Object!
;; Autocomplete expects the renderInput value to be function
;; returning React elements, not a component!
;; So reactify-component won't work here.
:render-input (fn [^js params]
;; Don't call js->clj because that would recursively
;; convert all JS objects (e.g. React ref objects)
;; to Cljs maps, which breaks them, even when converted back to JS.
;; Best thing is to use r/create-element and
;; pass the JS params to it.
;; If necessary, use JS interop to modify params.
(set! (.-variant params) "outlined")
(set! (.-label params) "Autocomplete")
(r/create-element mui/TextField params))}]])

In clojurescript, how to evaluate a list

Suppose having
(def defining-list `(def one 1))
How can I evaluate defining-list so that one becomes 1 ?
(in clojurescript)
EDIT:
I will give an idea of the broader image and what I am trying to accomplish here to avoid falling into an X/y problem.
I am trying to use cljsjs/material-ui from cljsjs package
Instead of defining each time a react component to use it as following:
(def app-bar
(r/adapt-react-class (aget js/MaterialUI (name :AppBar)))
I would like to define all the components from an array of tags:
(def material-ui-tags '[AppBar Avatar Backdrop])
So I was thinking if it's possible to do this without the usage of a macro as I found this
Something like:
(doseq [component material-ui-tags]
`(def ~(symbol (->kebab-case component)) (r/adapt-react-class (aget js/MaterialUI ~(name component)))))
But the above does only create a list of defs, I would like to evaluate these. In clojure eval would do the trick.
With reagent, you can use :> as shorthand for adapt-react-class as documented in https://github.com/reagent-project/reagent/blob/master/docs/InteropWithReact.md
Also, you can use dot notation with js/ and I think in shadow-cljs or cljs above 1.9.854 you can require to import the symbol instead of using aget.
In your case, it would be something like:
(ns example.core
(:require [MaterialUI]))
(defn component-two []
[:> MaterialUI/AppBar {:some-prop "some-value"}
[:div "children-here"]])
(defn component-two []
;; If the require above doesn't work
[:> js/MaterialUI.AppBar {:some-prop "some-value"}
[:div "children-here"]])
To do what you wanted using def, you either need eval or macro. Eval is not ideal as Jared Smith explained in the comment.
The example that you linked from reagent-material-ui uses macro. Invoking a macro actually performs expansion and then evaluation. So your code needs to be something like this:
clj file
(def material-ui-tags '[AppBar Avatar Backdrop])
(defmacro adapt-components []
(for [component material-ui-tags]
`(def ~(symbol (->kebab-case component)) (reagent.core/adapt-react-class (aget js/MaterialUI ~(name component))))))
cljs file
(adapt-components) ;; your defs will be available below this line
(defn my-component []
[app-bar ...])

Issues with getting both POST and GET http parameters in appengine-magic 0.4.3/Compojure 0.6.4

I'm having some serious issues with capturing POST and GET parameters with the latest appengine-magic/compojure versions. The parameters always come up as blank, even though the request object clearly has the right stuff in it.
I've seen some stuff around the interwebs about a change in the Compojure protocol where you have to manually put in the wrappers. I've tried this (using the handler/api wrapper to avoid the stuff in handler/site wrapper that breaks GAE) but it still doesn't work.
What am I doing wrong here?
My project.clj file:
(defproject pitch-filter "1.0.0-SNAPSHOT"
:description "FIXME: write description"
:dependencies [[org.clojure/clojure "1.2.0"]
[org.clojure/clojure-contrib "1.2.0"]
[compojure "0.6.4"]
[hiccup "0.3.6"]
[jtidy "4aug2000r7-dev"]
[commons-lang "2.5"]]
:dev-dependencies [[appengine-magic "0.4.2"]
[clj-http "0.1.1"]])
My core.clj file:
(ns pitch-filter.core
(:use compojure.core
[appengine-magic.multipart-params :only [wrap-multipart-params]]
[hiccup.middleware :only (wrap-base-url)])
(:require [pitch-filter.fetch :as fetch]
[compojure.route :as route]
[compojure.handler :as handler]
[appengine-magic.core :as ae]
[appengine-magic.services.url-fetch :as url]
(defroutes pitch-filter-app-routes
(GET "/" [] "Main Page")
(GET "/form" []
(str "<form method='post' action='/post'>"
"<input type='text' name='test'>"
"<input type='submit'>"
"</form>"))
(POST "/post" {params :params}
(pr-str params))
(route/not-found "Page not found"))
(def pitch-filter-app-handler
(-> pitch-filter-app-routes
(handler/api)
(wrap-base-url)
))
(ae/def-appengine-app pitch-filter-app #'pitch-filter-app-handler)
It certainly looks like dev_appserver.sh is busted with App Engine 1.5.1. Pity, but you aren't really supposed to use it with appengine-magic. You should use the interactive REPL tools (i.e., ae/serve). They work fine.
I updated the ticket you opened on the appengine-magic project page (https://github.com/gcv/appengine-magic/issues/28).
-gcv
Turns out that this issue only happens when using the Google App Engine 1.5.1 dev_appserver.sh. It works fine with 1.4.3. I'll leave this open in case someone else finds a better solution.

Simple example of Sandbar or Ring sessions in Google App Engine

I'm trying to work out how to get sessions and flash working in Google App Engine. Could someone provide a clear example using either Ring or Sandbar? I think I have sandbar working, specifically it doesn't tell me that Var sandbar.stateful-session/sandbar-flash is unbound and when I dump the handler I get :flash and :session though I'm not certain if that is a sandbar session or a ring one. For completeness I will mention that I am using the latest versions of appengine-magic, ring, hiccup and sandbar. There do not appear to be any incompatibilities or issues.
So a clear example preferably with use of flash-put!, flash-get, session-put! and session-get.
I don't usually like answering my own questions, however in this case I'll make an exception because:
a) There isn't a lot of easy to understand examples out there.
b) It would be nice to have a quick working example for others to use.
Note: appengine-magic is not required here, this will also work with normal ring sessions
Code
;; Place in a file called session.clj in the example project
(ns example.session
"Works with the normal ring sessions
allowing you to use side-effects to manage them")
(declare current-session)
(defn wrap-session! [handler]
(fn [request]
(binding [current-session (atom (or (:session request) {}))]
(let [response (handler request)]
(assoc response :session #current-session)))))
(defn session-get
([k] (session-get k nil))
([k default] (if (vector? k)
(get-in #current-session k)
(get #current-session k default))))
(defn session-put!
([m]
(swap! current-session (fn [a b] (merge a m)) m))
([k v]
(swap! current-session (fn [a b] (merge a {k b})) v)))
(defn session-pop! [k]
(let [res (get #current-session k)]
(swap! current-session (fn [a b] (dissoc a b)) k)
res))
(defn session-delete-key! [k]
(swap! current-session (fn [a b] (dissoc a b)) k))
(defn session-destroy! []
(swap! current-session (constantly nil)))
;; Place in a file called core.clj in the example project
(ns example.core
(:use compojure.core
[ring.middleware.keyword-params :only [wrap-keyword-params]]
[ring.middleware.session :only [wrap-session]]
[ring.middleware.session.cookie :only [cookie-store]]
[example session])
(:require [appengine-magic.core :as ae]))
(declare current-session)
(defroutes example-app-routes
(GET "/" _
(fn [req]
(let [counter (session-get :counter 0)]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (str "started: " counter)})))
(GET "/inc" _
(fn [req]
(let [counter (do
(session-put! :counter (inc (session-get :counter 0)))
(session-get :counter))]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (str "incremented: " counter)}))))
(def example-app-handler
(-> #'example-app-routes
wrap-keyword-params
wrap-session!
(wrap-session {:cookie-name "example-app-session"
:store (cookie-store)})))
(ae/def-appengine-app example-app #'example-app-handler)
How to use it
Navigating to http://127.0.0.1:8080/inc increments the counter in the session and http://127.0.0.1:8080/ will display the value of counter in the session.
wrap-session! is not required for sessions to work, just using
(wrap-session {:cookie-name "example-app-session"
:store (cookie-store)})
will give you working functional sessions. However I wanted to manage my sessions with side-effects and wrap-session! provides that functionality. To use flash like functionality, simply use session-put! to put a value into the session and then use session-pop! to remove it.
Hope that's helpful.
If you want to use the Sessions provided by GAE, you can use the following
https://gist.github.com/1095841
to include a ring like session in your requests, but based on GAE session support.
If you want stateful sessions on top of this, you can use the stateful session API provided by Sandbar
https://github.com/brentonashworth/sandbar

Resources