From b9b02e6edd67be2497b25f17b4228eb93b1a9ba6 Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Thu, 28 Sep 2017 13:26:27 +0100 Subject: [PATCH] solarized highlighting theme added to the docs build --- book.json | 3 + docs/CodeWalkthrough.md | 2 +- docs/Debugging.md | 36 ++--- docs/Effects.md | 2 +- docs/MentalModelOmnibus.md | 2 +- docs/SubscriptionsCleanup.md | 2 +- docs/Testing.md | 191 ++++++++++++++------------- docs/Using-Stateful-JS-Components.md | 2 +- docs/styles/website.css | 93 +++++++++++++ 9 files changed, 218 insertions(+), 115 deletions(-) create mode 100644 docs/styles/website.css diff --git a/book.json b/book.json index 457b680..d18a8a0 100644 --- a/book.json +++ b/book.json @@ -3,6 +3,9 @@ "description": "Documentation of re-frame framework", "language": "en", "root": "./docs/", + "styles": { + "website": "styles/website.css" + }, "structure": { "readme": "INTRO.md", "summary": "README.md" diff --git a/docs/CodeWalkthrough.md b/docs/CodeWalkthrough.md index 920ef5c..da6832b 100644 --- a/docs/CodeWalkthrough.md +++ b/docs/CodeWalkthrough.md @@ -60,7 +60,7 @@ here, to minimise cognitive load, we'll cut that corner. But ... we can't cut it completely. You'll still need an informal description, and here it is ... for this app `app-db` will contain a two-key map like this: -```cljs +```clj {:time (js/Date.) ;; current time for display :time-color "#f88"} ;; the colour in which the time should be shown ``` diff --git a/docs/Debugging.md b/docs/Debugging.md index e89fa48..37efa69 100644 --- a/docs/Debugging.md +++ b/docs/Debugging.md @@ -89,16 +89,16 @@ It is the functions within these namespaces that we wish to trace. 1. At the top of each add these namespaces, add these requires: - ```cljs - [clairvoyant.core :refer-macros [trace-forms]] - [re-frame-tracer.core :refer [tracer]] - ``` +```clojure + [clairvoyant.core :refer-macros [trace-forms]] + [re-frame-tracer.core :refer [tracer]] +``` 2. Then, immediately after the `ns` form add (if you want a green colour): - ```cljs - (trace-forms {:tracer (tracer :color "green")} - ``` +```clojure + (trace-forms {:tracer (tracer :color "green")} +``` 3. Finally, put in a closing `)` at the end of the file. Now all functions within the `ns` will be traced. It that is too noisy -- perhaps you won't want to trace all the helper functions -- @@ -107,15 +107,15 @@ around to suit your needs. 4. Colour choice - We have sauntered in the direction of the following colours +We have sauntered in the direction of the following colours - | file | colour| - |--------------|-------| - |`handlers.clj`| green | - |`subs.cljs` | brown | - |`views.clj` | gold | +| file | colour| +|--------------|-------| +|`handlers.clj`| green | +|`subs.cljs` | brown | +|`views.clj` | gold | - But I still think orange, flared pants are a good look. So, yeah. You may end up choosing others. +But I still think orange, flared pants are a good look. So, yeah. You may end up choosing others. ## Say No To Anonymous @@ -124,7 +124,8 @@ To get good quality tracing, you need to provide names for all your functions. So, don't let handlers be anonymous when registering them. For example, make sure you name the renderer in a Form2 component: -```clj + +```clojure (defn my-view [] (let [name (subscribe [:name])] @@ -133,6 +134,7 @@ For example, make sure you name the renderer in a Form2 component: ``` And name those event handlers: + ```clj (reg-event-db :blah @@ -209,7 +211,7 @@ you need to replace the macro `reaction` with the function `make-reaction`. Do the following code: -```cljs +```clj (ns my.ns (:require-macros [reagent.ratom :refer [reaction]])) @@ -224,7 +226,7 @@ Do the following code: needs to become -```cljs +```clj (ns my.ns (:require [reagent.ratom :refer [make-reaction]])) diff --git a/docs/Effects.md b/docs/Effects.md index 8f8dc10..90a08a5 100644 --- a/docs/Effects.md +++ b/docs/Effects.md @@ -49,7 +49,7 @@ the particular side effect required, and the `value` for that `key` provides further data. The structure of `value` is different for each side-effect. Here's the two instructions from the example above: -```cljs +```clj {:db (assoc db :flag a) ;; side effect on app-db :dispatch [:do-something-else 3]} ;; dispatch this event ``` diff --git a/docs/MentalModelOmnibus.md b/docs/MentalModelOmnibus.md index 35d1368..00a56db 100644 --- a/docs/MentalModelOmnibus.md +++ b/docs/MentalModelOmnibus.md @@ -103,7 +103,7 @@ into a collection, the events caused by that user's actions (button clicks, drags, key presses, etc). The collection of events might look like this: -```cljs +```clj (def collected-events [ [:clear] diff --git a/docs/SubscriptionsCleanup.md b/docs/SubscriptionsCleanup.md index bdf3d92..1666f64 100644 --- a/docs/SubscriptionsCleanup.md +++ b/docs/SubscriptionsCleanup.md @@ -96,7 +96,7 @@ AND there's less chance of us forgetting an `@` (which can lead to odd problems) I've ended up quite liking [the alternative names](https://lambdaisland.com/blog/11-02-2017-re-frame-form-1-subscriptions) suggested by [Lambda Island Videos](https://lambdaisland.com/): -```cljs +```clj (def evt re-frame.core/dispatch) ``` diff --git a/docs/Testing.md b/docs/Testing.md index 525753e..b1bba1e 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,35 +1,35 @@ ## Testing -This is an introduction to testing re-frame apps. It +This is an introduction to testing re-frame apps. It walks you through some choices. - + ## What To Test For any re-frame app, there's three things to test: - - **Event Handlers** - most of your testing focus will + - **Event Handlers** - most of your testing focus will be here because this is where most of the logic lives - - - **Subscription Handlers** - often not a lot to test here. Only - [Layer 2](SubscriptionInfographic.md) subscriptions need testing. - + + - **Subscription Handlers** - often not a lot to test here. Only + [Layer 2](SubscriptionInfographic.md) subscriptions need testing. + - **View functions** - I don't tend to write tests for views. There, I said it. - Hey! It is mean to look at someone with that level of disapproval, + Hey! It is mean to look at someone with that level of disapproval, while shaking your head. I have my reasons ...
In my experience with the re-frame architecture, View Functions tend to be an unlikely source of bugs. And every line of code you write is - like a ball & chain you must forevermore drag about, so I dislike maintaining + like a ball & chain you must forevermore drag about, so I dislike maintaining tests which don't deliver good bang for buck. -And, yes, in theory there's also `Effect Handlers` (Domino 3) to test, -but you'll hardly ever write one, and, anyway, each one is different, so +And, yes, in theory there's also `Effect Handlers` (Domino 3) to test, +but you'll hardly ever write one, and, anyway, each one is different, so I've got no good general insight to offer you for them. They will be ignored -in this tutorial. - +in this tutorial. + ## Test Terminology -Let's establish some terminology to aid the further explanations in this -tutorial. Every unittest has 3 steps: +Let's establish some terminology to aid the further explanations in this +tutorial. Every unittest has 3 steps: 1. **setup** initial conditions 2. **execute** the thing-under-test 3. **verify** that the thing-under-test did the right thing @@ -39,6 +39,7 @@ tutorial. Every unittest has 3 steps: Event Handlers are pure functions which should make them easy to test, right? First, create a named event handler using `defn` like this: + ```clj (defn select-triangle [db [_ triangle-id] @@ -46,134 +47,137 @@ First, create a named event handler using `defn` like this: ``` You'd register this handler in a separate step: + ```clj (re-frame.core/reg-event-db ;; this is a "-db" event handler, not "-fx" - :select-triangle + :select-triangle [some-interceptors] select-triangle) ;; <--- defn above. don't use an annonomous fn ``` -This arrangement means the event handler function +This arrangement means the event handler function `select-triangle` is readily available to be unittested. ## Event Handlers - Setup - Part 1 -To test `select-triangle`, a unittest must pass in values for the two arguments +To test `select-triangle`, a unittest must pass in values for the two arguments `db` and `v`. And, so, our **setup** would have to construct both values. -But how to create a useful `db` value? +But how to create a useful `db` value? -`db` is a map of a certain structure, so one way would be to simply `assoc` values -into a map at certain paths to simulate a real-world `db` value or, even easier, just use +`db` is a map of a certain structure, so one way would be to simply `assoc` values +into a map at certain paths to simulate a real-world `db` value or, even easier, just use a map literal, like this: -```cljs +```clj ;; a test (let [ ;; setup - create db and event db {:some 42 :thing "hello"} ; a literal event [:select-triange :other :event :args] - + ;; execute - result-db (select-triange db event)] - + result-db (select-triange db event)] + ;; validate that result-db is correct) (is ...) ``` -This certainly works in theory, but in practice, -unless we are careful, constructing the `db` +This certainly works in theory, but in practice, +unless we are careful, constructing the `db` value in **setup** could: - * be manual and time consuming + * be manual and time consuming * tie tests to the internal structure of `app-db` The **setup** of every test could end up relying on the internal structure -of `app-db` and any change in that structure (which is inevitable over time) +of `app-db` and any change in that structure (which is inevitable over time) would result in a lot re-work in the tests. That's too fragile. -So, this approach doesn't quite work. +So, this approach doesn't quite work. ## Event Handlers - Setup - Part 2 -> In re-frame, `Events` are central. They are the "language of the system". They -provide the eloquence. - +> In re-frame, `Events` are central. They are the "language of the system". They +provide the eloquence. + The `db` value (stored in `app-db`) is the cumulative result of many event handlers running. -We can use this idea. In **setup**, instead of manually trying to create that `db` value, we could +We can use this idea. In **setup**, instead of manually trying to create that `db` value, we could "build up" a `db` value by threading `db` through many event handlers which cumulatively create the required initial state. Tests then need know nothing about the internal structure of that `db`. Like this: + ```clj (let [ - ;; setup - cummulatively build up db - db (-> {} ;; empty db - (initialise-db [:initialise-db]) ;; each event handler expects db and event + ;; setup - cummulatively build up db + db (-> {} ;; empty db + (initialise-db [:initialise-db]) ;; each event handler expects db and event (clear-panel [:clear-panel]) (draw-triangle [:draw-triangle 1 2 3])) - + event [:select-triange :other :stuff] - + ;; now execute the event handler under test db' (select-triange db event)] - + ;; validate that db' is correct (is ...) + ``` This approach works so long as all the event handlers are -of the `-db` kind, but the threading gets a little messy when some event -handlers are of the `-fx` kind which take a `coeffect` argument and -return `effects`, instead of a `db` value. +of the `-db` kind, but the threading gets a little messy when some event +handlers are of the `-fx` kind which take a `coeffect` argument and +return `effects`, instead of a `db` value. So, this approach is quite workable in some cases, but can get messy -in the general case. +in the general case. ## Event Handlers - Setup - Part 3 There is further variation which is quite general but not as pure. -During test **setup** we could literally just `dispatch` the events -which would put `app-db` into the right state. +During test **setup** we could literally just `dispatch` the events +which would put `app-db` into the right state. -Except, we'd have to use `dispatch-sysnc` rather `dispatch` to -force immediate handling of events, rather than queuing. +Except, we'd have to use `dispatch-sysnc` rather `dispatch` to +force immediate handling of events, rather than queuing. ```clj - ;; setup - cummulatively build up db - (dispatch-sync [:initialise-db]) - (dispatch-sync [:clear-panel]) - (dispatch-sync [:draw-triangle 1 2 3])) - - ;; execute - (dispatch-sync [:select-triange :other :stuff]) +;; setup - cummulatively build up db +(dispatch-sync [:initialise-db]) +(dispatch-sync [:clear-panel]) +(dispatch-sync [:draw-triangle 1 2 3]) - ;; validate that the valuein `app-db` is correct - ;; perhaps with subscriptions +;; execute +(dispatch-sync [:select-triange :other :stuff]) + +;; validate that the valuein 'app-db' is correct +;; perhaps with subscriptions ``` Notes: - 1. we use `dispatch-sync` because `dispatch` is async (event is handled not now, but soon) + 1. we use `dispatch-sync` because `dispatch` is async (event is handled not now, but soon) 2. Not pure. We are choosing to mutate the global `app-db`. But - having said that, there's something about this approach with is remarkably + having said that, there's something about this approach with is remarkably pragmatic. 2. the **setup** is now very natural. The associated handlers can be either `-db` or `-fx` 3. if the handlers have effects other than just updating app-db, we might need to stub out XXX - 4. How do we look at the results ???? + 4. How do we look at the results ???? If this method appeals to you, you should ABSOLUTELY review the utilities in this helper library: [re-frame-test](https://github.com/Day8/re-frame-test). In summary, event handlers should be easy to test because they are pure functions. The interesting -part is the unittest "setup" where we need to establishing an initial value for `db`. +part is the unittest "setup" where we need to establishing an initial value for `db`. -## Subscription Handlers +## Subscription Handlers -Here's a Subscription Handler from +Here's a Subscription Handler from [the todomvc example](https://github.com/Day8/re-frame/blob/master/examples/todomvc/src/todomvc/subs.cljs): ```clj @@ -196,10 +200,10 @@ Here's a Subscription Handler from How do we test this? -First, we could split the computation function from its registration, like this: +First, we could split the computation function from its registration, like this: ```clj (defn visible-todos - [[todos showing] _] + [[todos showing] _] (let [filter-fn (case showing :active (complement :done) @@ -215,20 +219,20 @@ First, we could split the computation function from its registration, like this: visible-todos) ;; <--- computation function used here ``` -That makes `visible-todos` available for direct unit testing. But, as we experienced +That makes `visible-todos` available for direct unit testing. But, as we experienced with Event Handlers, the challenge is around constructing `db` values (first parameter) -in a way which doesn't become fragile. +in a way which doesn't become fragile. ## View Functions - Part 1 Components/views are more tricky and there are a few options. -But remember my ugly secret - I don't tend to write tests for my views. +But remember my ugly secret - I don't tend to write tests for my views. But here's how, theoretically, I'd write tests if I wasn't me ... If a View Function is [Form-1](https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-1-a-simple-function), -then it is fairly easy to test. +then it is fairly easy to test. A trivial example: ```clj @@ -241,9 +245,9 @@ A trivial example: ``` So, here, testing involves passing values into the function and checking the data structure returned -for correctness. +for correctness.p -What's returned is hiccup, of course. So how do you test hiccup for correctness? +What's returned is hiccup, of course. So how do you test hiccup for correctness? hiccup is just a clojure data structure - vectors containing keywords, and maps, and other vectors, etc. Perhaps you'd use https://github.com/nathanmarz/specter to declaratively check on the presence @@ -251,34 +255,35 @@ of certain values and structures? Or do it more manually. ## View Functions - Part 2A -But what if the View Function has a subscription? +But what if the View Function has a subscription? ```clj (defn my-view - [something] - (let [val (subscribe [:query-id])] <-- reactive subscription - [:div .... using @val in here]))) + [something] + (let [val (subscribe [:query-id])] ;; <-- reactive subscription + [:div .... using @val in here])) ``` The use of `subscribe` makes the function impure (it obtains data from places other than its args). -A testing plan might be: +A testing plan might be: 1. setup `app-db` with some values in the right places (via dispatch of events?) 2. call `my-view` (with a parameter) which will return hiccup - 3. check the hiccup structure for correctness. - + 3. check the hiccup structure for correctness. + Continuing on, in a second phase you could then: 5. change the value in `app-db` (which will cause the subscription to fire) - 6. call view functions again (hiccup returned). - 7. check the new hiccup for correctness + 6. call view functions again (hiccup returned). + 7. check the new hiccup for correctness -Which is all possible, if a little messy. +Which is all possible, if a little messy. ## View Functions - Part 2B -There is a pragmatic method available to handle the impurity: use `with-redefs` +There is a pragmatic method available to handle the impurity: use `with-redefs` to stub out `subscribe`. Like this: + ```clj (defn subscription-stub [x] (atom @@ -292,28 +297,28 @@ to stub out `subscribe`. Like this: ``` For more integration level testing, you can use `with-mounted-component` -from the [reagent-template](https://github.com/reagent-project/reagent-template/blob/master/src/leiningen/new/reagent/test/cljs/reagent/core_test.cljs) -to render the component in the browser and validate the generated DOM. +from the [reagent-template](https://github.com/reagent-project/reagent-template/blob/master/src/leiningen/new/reagent/test/cljs/reagent/core_test.cljs) +to render the component in the browser and validate the generated DOM. ## View Functions - Part 2C -Or ... there is another option: you can structure in the first place for pure view functions. +Or ... there is another option: you can structure in the first place for pure view functions. -The trick here is to create an outer and inner component. The outer sources the data +The trick here is to create an outer and inner component. The outer sources the data (via a subscription), and passes it onto the inner as props (parameters). -As a result, the inner component, which does the testable work, is pure and +As a result, the inner component, which does the testable work, is pure and easily tested. The outer is impure but trivial. -To get a more concrete idea, I'll direct you to another page in the re-frame docs -which has nothing to do with testing, but it does use this `simple-outer-subscribe-with-complicated-inner-render` -pattern for a different purpose: +To get a more concrete idea, I'll direct you to another page in the re-frame docs +which has nothing to do with testing, but it does use this `simple-outer-subscribe-with-complicated-inner-render` +pattern for a different purpose: [Using Stateful JS Components](Using-Stateful-JS-Components.md) - -Note: this technique could be made simple and almost invisible via the + +Note: this technique could be made simple and almost invisible via the use of macros. -This pattern has been independently discovered by many. For example, here +This pattern has been independently discovered by many. For example, here it is called the [Container/Component pattern](https://medium.com/@learnreact/container-components-c0e67432e005#.mb0hzgm3l). diff --git a/docs/Using-Stateful-JS-Components.md b/docs/Using-Stateful-JS-Components.md index f8de1f0..7e0c423 100644 --- a/docs/Using-Stateful-JS-Components.md +++ b/docs/Using-Stateful-JS-Components.md @@ -23,7 +23,7 @@ The pattern involves the outer component, which sources data, supplying this dat ### Example Using Google Maps -```cljs +```clj (defn gmap-inner [] (let [gmap (atom nil) options (clj->js {"zoom" 9}) diff --git a/docs/styles/website.css b/docs/styles/website.css new file mode 100644 index 0000000..d7ef7c9 --- /dev/null +++ b/docs/styles/website.css @@ -0,0 +1,93 @@ +/* .hljs-symbol { */ +/* color: #AD81FF !important; */ +/* } */ + +/* .hljs-name { */ +/* color: #67D8EE !important; */ +/* } */ + +/* .hljs-builtin-name { */ +/* color: #A6E131; */ +/* } */ +.markdown-section > pre { + color: #657b83 !important; + background: #fdf6e3 !important; +} +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #fdf6e3; + color: #657b83; +} + +.hljs-comment, +.hljs-quote { + color: #93a1a1; +} + +/* Solarized Green */ +.hljs-keyword, +.hljs-selector-tag, +.hljs-addition { + color: #859900; +} + +/* Solarized Cyan */ +.hljs-number, +.hljs-string, +.hljs-meta .hljs-meta-string, +.hljs-literal, +.hljs-doctag, +.hljs-regexp { + color: #2aa198; +} + +/* Solarized Blue */ +.hljs-title, +.hljs-section, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #268bd2; +} + +/* Solarized Yellow */ +.hljs-attribute, +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-class .hljs-title, +.hljs-type { + color: #b58900; +} + +/* Solarized Orange */ +.hljs-symbol, +.hljs-bullet, +.hljs-subst, +.hljs-meta, +.hljs-meta .hljs-keyword, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-link { + color: #cb4b16; +} + +/* Solarized Red */ +.hljs-built_in, +.hljs-deletion { + color: #dc322f; +} + +.hljs-formula { + background: #eee8d5; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +}