Merge branch 'develop'

This commit is contained in:
Mike Thompson 2017-06-02 15:26:12 +10:00
commit d26c6f5057
3 changed files with 156 additions and 54 deletions

View File

@ -18,6 +18,16 @@ y'know. Pretty good.
[![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/develop.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/develop) [![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/develop.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/develop)
[![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/master.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/master) [![Circle CI](https://circleci.com/gh/Day8/re-frame/tree/master.svg?style=shield&circle-token=:circle-ci-badge-token)](https://circleci.com/gh/Day8/re-frame/tree/master)
## re-frame
re-frame is a pattern for writing [SPAs] in ClojureScript, using [Reagent].
McCoy might report "It's MVC, Jim, but not as we know it". And you would respond
"McCoy, you trouble maker, why even mention an OO pattern?
re-frame is a **functional framework**."
Being a functional framework, it is about data, and the functions
which transform that data.
## Why Should You Care? ## Why Should You Care?
@ -80,18 +90,7 @@ order functions). Etc.
**Data - that's the way we roll.** **Data - that's the way we roll.**
## re-frame ## It is a loop
re-frame is a pattern for writing [SPAs] in ClojureScript, using [Reagent].
McCoy might report "It's MVC, Jim, but not as we know it". And you would respond
"McCoy, you trouble maker, why even mention an OO pattern?
re-frame is a **functional framework**."
Being a functional framework, it is about data, and the functions
which transform that data.
### It is a loop
Architecturally, re-frame implements "a perpetual loop". Architecturally, re-frame implements "a perpetual loop".

View File

@ -6,37 +6,24 @@ Please add to this list by submitting a pull request.
### Templates ### Templates
* [re-frame-template](https://github.com/Day8/re-frame-template) - Generates the client side SPA * [re-frame-template](https://github.com/Day8/re-frame-template) - Generates the client side SPA
* [Luminus](http://www.luminusweb.net) - Generates SPA plus server side. * [Luminus](http://www.luminusweb.net) - Generates SPA plus server side.
* [re-natal](https://github.com/drapanjanas/re-natal) - React Native apps. * [re-natal](https://github.com/drapanjanas/re-natal) - React Native apps.
* [Slush-reframe](https://github.com/kristianmandrup/slush-reframe) - A scaffolding generator for re-frame run using NodeJS. Based on re-frame `0.7.0`
* [Slush-reframe](https://github.com/kristianmandrup/slush-reframe) - A scaffolding generator for re-frame run using NodeJS. Should work wih re-frame `0.7.0` if used on a project started from the `0.7.0` version of re-frame-template.
* [Celibidache](https://github.com/velveteer/celibidache/) - An opinionated starter for re-frame applications using Boot. Based on re-frame `0.7.0` * [Celibidache](https://github.com/velveteer/celibidache/) - An opinionated starter for re-frame applications using Boot. Based on re-frame `0.7.0`
### Examples and Applications Using re-frame ### Examples and Applications Using re-frame
* [How to create decentralised apps with re-frame and Ethereum](https://medium.com/@matus.lestan/how-to-create-decentralised-apps-with-clojurescript-re-frame-and-ethereum-81de24d72ff5#.b9xh9xnis) - Tutorial with links to code and live example. * [How to create decentralised apps with re-frame and Ethereum](https://medium.com/@matus.lestan/how-to-create-decentralised-apps-with-clojurescript-re-frame-and-ethereum-81de24d72ff5#.b9xh9xnis) - Tutorial with links to code and live example.
* [Elfeed-cljsrn](https://github.com/areina/elfeed-cljsrn) - A mobile client for [Elfeed](https://github.com/skeeto/elfeed) rss reader, built with React Native. * [Elfeed-cljsrn](https://github.com/areina/elfeed-cljsrn) - A mobile client for [Elfeed](https://github.com/skeeto/elfeed) rss reader, built with React Native.
* [Memory Hole](https://github.com/yogthos/memory-hole) - A small issue tracking app written with Luminus and re-frame. * [Memory Hole](https://github.com/yogthos/memory-hole) - A small issue tracking app written with Luminus and re-frame.
* [Crossed](https://github.com/velveteer/crossed/) - A multiplayer crossword puzzle generator. Based on re-frame `0.7.0` * [Crossed](https://github.com/velveteer/crossed/) - A multiplayer crossword puzzle generator. Based on re-frame `0.7.0`
* [imperimetric](https://github.com/Dexterminator/imperimetric) - Webapp for converting texts with some system of measurement to another, such as imperial to metric. * [imperimetric](https://github.com/Dexterminator/imperimetric) - Webapp for converting texts with some system of measurement to another, such as imperial to metric.
* [Brave Clojure Open Source](https://github.com/braveclojure/open-source) A site using re-frame, liberator, boot and more to display active github projects that powers [http://open-source.braveclojure.com](http://open-source.braveclojure.com). Based on re-frame `0.6.0` * [Brave Clojure Open Source](https://github.com/braveclojure/open-source) A site using re-frame, liberator, boot and more to display active github projects that powers [http://open-source.braveclojure.com](http://open-source.braveclojure.com). Based on re-frame `0.6.0`
* [flux-challenge with re-frame](https://github.com/staltz/flux-challenge/tree/master/submissions/jelz) - flux-challenge is "a frontend challenge to test UI architectures and solutions". re-frame `0.5.0`
* [flux-challenge with re-frame](https://github.com/staltz/flux-challenge/tree/master/submissions/jelz) - flux-challenge is "a frontend challenge to test UI architectures and solutions". This is a ClojureScript + re-frame version. Based on re-frame `0.5.0`
* [fractalify](https://github.com/madvas/fractalify/) - * [fractalify](https://github.com/madvas/fractalify/) -
An entertainment and educational webapp for creating & sharing fractal images that powers [fractalify.com](http://fractalify.com). Based on re-frame `0.4.1` An entertainment and educational webapp for creating & sharing fractal images that powers [fractalify.com](http://fractalify.com). Based on re-frame `0.4.1`
* [Angular Phonecat tutorial in re-frame](http://dhruvp.github.io/2015/03/07/re-frame/) - A detailed step-by-step tutorial that ports the Angular Phonecat tutorial to re-frame. Based on re-frame `0.2.0` * [Angular Phonecat tutorial in re-frame](http://dhruvp.github.io/2015/03/07/re-frame/) - A detailed step-by-step tutorial that ports the Angular Phonecat tutorial to re-frame. Based on re-frame `0.2.0`
* [Braid](https://github.com/braidchat/braid) - A new approach to group chat, designed around conversations and tags instead of rooms. * [Braid](https://github.com/braidchat/braid) - A new approach to group chat, designed around conversations and tags instead of rooms.
### Effect and CoEffect Handlers ### Effect and CoEffect Handlers
@ -57,13 +44,13 @@ Please add to this list by submitting a pull request.
### Tools, Techniques & Libraries ### Tools, Techniques & Libraries
* [re-frame-undo](https://github.com/Day8/re-frame-undo) - An undo library for re-frame * [re-frame-undo](https://github.com/Day8/re-frame-undo) - An undo library for re-frame
* Animation using `react-flip-move` - http://www.upgradingdave.com/blog/posts/2016-12-17-permutation.html * [re-frame-test](https://github.com/Day8/re-frame-test) - Advanced testing utilities
* [Animation](http://www.upgradingdave.com/blog/posts/2016-12-17-permutation.html) using `react-flip-move`
* [re-frisk](https://github.com/flexsurfer/re-frisk) - A library for visualizing re-frame data and events. * [re-frisk](https://github.com/flexsurfer/re-frisk) - A library for visualizing re-frame data and events.
* [re-thread](https://github.com/yetanalytics/re-thread) - A library for running re-frame applications in Web Workers. * [re-thread](https://github.com/yetanalytics/re-thread) - A library for running re-frame applications in Web Workers.
* [re-frame-datatable](https://github.com/kishanov/re-frame-datatable) - DataTable UI component built for use with re-frame. * [re-frame-datatable](https://github.com/kishanov/re-frame-datatable) - DataTable UI component built for use with re-frame.
* [Stately: State Machines](https://github.com/nodename/stately) also https://www.youtube.com/watch?v=klqorRUPluw * [Stately: State Machines](https://github.com/nodename/stately) also https://www.youtube.com/watch?v=klqorRUPluw
* [re-frame-test](https://github.com/Day8/re-frame-test) - Integration Testing (not documented) * [re-learn](https://github.com/oliyh/re-learn) - Data driven tutorials for educating users of your reagent / re-frame app
* [re-learn](https://github.com/oliyh/re-learn) - Data driven tutorials for educating users of your reagent / re-frame app, built with re-frame
### Videos ### Videos

View File

@ -1,35 +1,147 @@
## Testing ## Testing
This is an introductory, simple exploration of testing re-frame apps. If you want some more help see [re-frame-test](https://github.com/Day8/re-frame-test) This is an introduction to testing re-frame apps.
## What To Test
With a re-frame app, there's principally three things to test: For any re-frame app, there's three things to test:
1. Event handlers
2. Subscription handlers
3. View functions
## Event Handlers - Part 1 - **Event Handlers** - most of your focus goes here because its where
most of the logic lives
- **Subscription Handlers** - often not a lot to test
- **View functions** - I don't tend to write tests for views. There, I said it.
Hey, its mean to look at someone with that level of disapproval. I have my reasons.
In my experience with the re-frame architecture, the 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 hate maintaining
tests which don't deliver good bang for buck.
Yes, in theory there are also `Effect Handlers` (Domino 3) to test,
but you'll hardly ever write one, and
anyway, by nature, they are messy, mutative by design and all different,
so I've got no good general insight to offer,
other than make them small and simple, and do your best testing them.
Event Handlers are pure functions and consequently easy to test. ## Test Terminology
First, create an event handler like this: Let's establish some terminology. Every unittest has 3 parts:
1. **setup** initial conditions
2. **execute** the thing-under-test
3. **verify** that the thing-under-test did the right thing
Below, I'll be referring to those 3 parts.
## Event Handlers
Event Handlers are pure functions and consequently should be easy to test.
First, create the event handler via `defn` like this:
```clj ```clj
(defn my-db-handler (defn select-triangle
[db v] [db [_ triangle-id]
... return a modified version of db) ... return a modified version of db)
``` ```
Then, register it in a separate step: Then, register this handler in a separate step:
```clj ```clj
(re-frame.core/reg-event-db (re-frame.core/reg-event-db
:some-id :select-triangle
[some-interceptors] [some-interceptors]
my-db-handler) select-triangle) ;; <--- defn above. don't use an annonomous fn
``` ```
With this setup, `my-db-handler` is available for direct testing. This arrangement is good because it means the event handler function
`select-triangle` is readily available to be unittested.
Your unittests will pass in certain values for `db` and `v`, ## Event Handlers - Setup - Part 1
and then ensure it returns the right (modified version of) `db`.
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 the necessary
`db` and `event` values.
But how to create a `db` value?
`db` is a map of a certain structure, so one way would be to `assoc` values
into a map at certain paths to simulate a production `db` value, or just use
a map literal, like this:
```cljs
;; a test
(let [
;; setup
db {:some 42 :thing "hello"} ; a literal
event [:select-triange :other :stuff]
;; execute
db' (select-triange db event)]
;; validate that db' is correct)
```
While this works in theory, in practice,
unless we are very careful, constructing the `db`
value in the **setup** phase could:
* be manual and time consuming
* tie tests to the internal structure of `app-db`
Every test would end up with knowledge about the internal structure
of `app-db` and any change in that structure (which is inevitable over time)
would result in a lot busy work re-coding the **setup** code in every test.
So, we'll need a better way of handling the **setup** ...
## Event Handlers - Setup - Part 2
In a re-frame app, the `db` value (stored in `app-db`) is created by the cumulative
actions of successive event handlers.
So, in **setup** we could "build up" a `db` value by calling multiple event handlers to
cumulatively create the required state. Then our test does not have to know much about
app-db's structure.
```clj
(let [
;; setup - cummulatively build up db by threading it through event handlers
db (-> {} ;; empty db
(initialise-db [:initialise-db])
(clear-panel [:clear-panel])
(draw-triangle [:draw-triangle 1 2 3]))
event [:select-triange :other :stuff]
;; execute
db' (select-triange db event)]
;; validate that db' is correct)
```
This approach works so long as all the event handlers are
of the `-db` kind, but it gets messy when some event handlers are of the `-fx` kind, because
`db` can't be neatly threaded. (The `-fx` handlers take a coeffect argument and
return effects).
## Event Handlers - Setup - Part 3
So now we now to the final variation of **setup**, and probably the best.
Instead of calling the
XXXX use duispatch
XXX this won't work because dispatch happens shortly
XXX so tehn use dispatch-sync
XXX still problem is
XXX how to look at the results ? Reach into app-db ?
XXX
For your `-fx` event handlers, you'll use the same strategy as that
outlined above for `-db` handlers. The arguments change to be `coeffects` and `v`,
and the return value is a map of `effects`, but the overall strategy
is the same.
And, if you want to get more advanced, see the utilities in
[re-frame-test](https://github.com/Day8/re-frame-test).
## Subscription Handlers ## Subscription Handlers
@ -74,7 +186,7 @@ We could split the computation function from its registration, like this:
visible-todos) ;; <--- computation function used here visible-todos) ;; <--- computation function used here
``` ```
That makes `visible-todos` available for direct unit testing. That makes `visible-todos` available for direct unit testing.
## View Functions - Part 1 ## View Functions - Part 1
@ -83,10 +195,13 @@ Components/views are slightly more tricky. There's a few options.
First, I have to admit an ugly secret. I don't tend to write tests for my views. First, I have to admit an ugly secret. I don't tend to write tests for my views.
Hey, don't give me that disproving frown! I have my reasons. Hey, don't give me that disproving frown! I have my reasons.
Remember that every line of code you write is a liability. So tests have to earn **My Reasons:** every line of code you write is a liability. So tests have
their keep - they have to deliver a good cost / benefit ratio. And, in my experience to earn their keep by delivering a good cost / benefit ratio.
with the re-frame architecture, the Reagent view components tend to be an unlikely And, in my experience with the re-frame architecture, the View Functions
source of bugs. There's just not much logic in them for me to get wrong. tend to be an unlikely source of bugs. There's just not much logic in
them for me to get wrong because the re-frame philosophy is very much to
keep view functions as dumb as possible.
Okay, fine, don't believe me, then!! Okay, fine, don't believe me, then!!
@ -171,10 +286,11 @@ The trick here is to create an outer and inner component. The outer sources the
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 fairly trivial. easily tested. The outer is fairly trivial.
To get a more concrete idea, I'll direct you to another page on this Wiki To get a more concrete idea, I'll direct you to another page in the docs
which has nothing to do with testing, but it does use this `simple-outer-subscribe-with-complicated-inner-render` 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]] 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. (Contribute one if you have it). use of macros. (Contribute one if you have it).