Merge branch 'develop'
This commit is contained in:
commit
d26c6f5057
23
README.md
23
README.md
|
@ -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".
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
164
docs/Testing.md
164
docs/Testing.md
|
@ -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).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue