test(react-impl): add react-impl

This commit is contained in:
defclass
2021-02-23 21:12:21 +08:00
parent f8deee4473
commit b108b82903
2 changed files with 145 additions and 50 deletions

View File

@@ -0,0 +1,95 @@
(ns frontend.react-impl
"Note: don't run component parallel"
;;#?(:clj (:require [clojure.tools.macro :refer [name-with-attributes]]))
#?(:cljs (:require-macros [frontend.react-impl])))
(defn name-with-attributes
"copy: https://github.com/clojure/tools.macro/blob/master/src/main/clojure/clojure/tools/macro.clj#L282"
[name macro-args]
(let [[docstring macro-args] (if (string? (first macro-args))
[(first macro-args) (next macro-args)]
[nil macro-args])
[attr macro-args] (if (map? (first macro-args))
[(first macro-args) (next macro-args)]
[{} macro-args])
attr (if docstring
(assoc attr :doc docstring)
attr)
attr (if (meta name)
(conj (meta name) attr)
attr)]
[(with-meta name attr) macro-args]))
#_{:component-key {:result nil
:watches []
:f-path nil
:f nil}}
(def react-defines (atom {}))
(def ^:dynamic *f-indent* nil)
(def ^:dynamic *react-f-path* '())
(def ^:dynamic *from-watching-fn* nil)
(defn react'
[react-ref]
(let [ident *f-indent*
f (get-in @react-defines [ident :f])]
(cond
(= true *from-watching-fn*)
(deref react-ref)
(ifn? f)
(let [component (get @react-defines ident)]
(when-not ((:watches component) react-ref)
(let [new-component (update component :watches conj react-ref)]
(add-watch react-ref react-ref
(fn [& _]
(binding [*from-watching-fn* true]
(reset! (:result component) (f))
(let [f-path (rest (get-in @react-defines [ident :f-path]))]
(doseq [{:keys [f ident]} f-path]
(binding [*f-indent* ident]
(let [component (get @react-defines ident)]
(reset! (:result component) (f)))))))))
(swap! react-defines assoc *f-indent* new-component)
@react-ref)))
:else (deref react-ref))))
(defn react-fn
[f]
(let [result-ref (atom nil)
ident (keyword (gensym))]
(binding [*f-indent* ident
*react-f-path* (conj *react-f-path* {:f f :ident ident})]
(swap! react-defines assoc *f-indent* {:result result-ref
:watches #{}
:f-path *react-f-path*
:f f})
(reset! result-ref (f))
(deref (get-in @react-defines [ident :result])))))
#?(:clj (defmacro react
[react-ref]
`(react' ~react-ref)))
#?(:clj (defmacro defc
[sym args & body]
`(defn ~sym ~args
(fn []
(let [f# (fn [] ~@body)]
(react-fn f#))))))
#?(:clj (defmacro auto-clean-state
[& body]
`(do (reset! react-defines {})
(let [result# ~@body]
(reset! react-defines {})
result#))))
(comment
(def b (atom 1))
(defc inner
[c]
(+ c (react b))))

View File

@@ -1,60 +1,60 @@
(ns frontend.react-impl-test
"To facilitate testing, imitate the behavior of react")
"To facilitate testing, imitate the behavior of react"
(:require [frontend.react-impl :as r]
[cljs.test :refer [deftest is are testing use-fixtures]]))
;{:component-key {:result nil
; :watches []}}
(def react-defines (atom {}))
(def ^:dynamic *react-fn* nil)
(deftest simple-react-test
(r/auto-clean-state
(let [react-ref (atom 1)]
(defn react
[react-ref]
(let [f *react-fn*]
(cond
(= :from-watching-fn f)
@react-ref
(r/defc simple-component
[]
(+ 2 (r/react react-ref)))
(ifn? f)
(let [component (get @react-defines f)]
(when-not ((:watches component) react-ref)
(let [new-component (update component :watches conj react-ref)]
(add-watch react-ref react-ref
(fn [& _]
(binding [*react-fn* :from-watching-fn]
(reset! (:result component) (f)))))
(swap! react-defines assoc f new-component)
@react-ref)))
(let [get-result (simple-component)]
:else (deref react-ref))))
(is (= 3 (get-result)))
(reset! react-ref 2)
(is (= 4 (get-result)))))))
(defn react-fn
[f]
{:pre [(fn? f)]}
(let [result-ref (atom nil)
;; Each react-fn invoke will generate new *react-fn*, though the same f.
f' (fn [] (f))]
(binding [*react-fn* f']
(swap! react-defines assoc f' {:result result-ref
:watches #{}})
(reset! result-ref (f'))
{:clear-state-fn
(fn [] (-> (swap! react-defines dissoc f')
(empty?)))
:get-value-fn
(fn [] (deref (get-in @react-defines [f' :result])))})))
(deftest nest-component-test
(r/auto-clean-state
(let [a (atom 1)
b (atom 2)]
(defn clear-react-resources
[]
(reset! react-defines nil))
(r/defc inner
[]
(r/react b))
(comment
(let [react-ref (atom 1)
f (fn []
(let [haha (react react-ref)]
(* haha 2)))
{:keys [clear-state-fn get-value-fn]} (react-fn f)]
(prn (get-value-fn))
(reset! react-ref 2)
(prn (get-value-fn))
(clear-state-fn)))
(r/defc out
[]
(let [out (r/react a)
get-inner-result (inner)]
(+ out (get-inner-result))))
(let [get-out-result (out)]
(is (= 3 (get-out-result)))
(reset! b 4)
(is (= 5 (get-out-result)))))))
(deftest defc-params-test
(r/auto-clean-state
(let [a (atom 1)
b (atom 2)]
(r/defc inner-1
[c]
(+ c (r/react b)))
(r/defc out-1
[]
(let [out (r/react a)
get-inner-result (inner-1 5)]
(+ out (get-inner-result))))
(let [get-out-result (out-1)]
(is (= 8 (get-out-result)))
(reset! b 4)
(is (= 10 (get-out-result)))))))