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/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?
|
||||
|
||||
|
@ -80,18 +90,7 @@ order functions). Etc.
|
|||
**Data - that's the way we roll.**
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
### It is a loop
|
||||
## It is a loop
|
||||
|
||||
Architecturally, re-frame implements "a perpetual loop".
|
||||
|
||||
|
|
|
@ -6,37 +6,24 @@ Please add to this list by submitting a pull request.
|
|||
### Templates
|
||||
|
||||
* [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.
|
||||
|
||||
* [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. Should work wih re-frame `0.7.0` if used on a project started from the `0.7.0` version of re-frame-template.
|
||||
|
||||
* [Slush-reframe](https://github.com/kristianmandrup/slush-reframe) - A scaffolding generator for re-frame run using NodeJS. 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
|
||||
|
||||
* [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.
|
||||
|
||||
* [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`
|
||||
|
||||
* [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`
|
||||
|
||||
* [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`
|
||||
|
||||
* [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`
|
||||
* [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`
|
||||
|
||||
* [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.
|
||||
|
||||
### Effect and CoEffect Handlers
|
||||
|
@ -57,13 +44,13 @@ Please add to this list by submitting a pull request.
|
|||
### Tools, Techniques & Libraries
|
||||
|
||||
* [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-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.
|
||||
* [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, built with re-frame
|
||||
* [re-learn](https://github.com/oliyh/re-learn) - Data driven tutorials for educating users of your reagent / re-frame app
|
||||
|
||||
### Videos
|
||||
|
||||
|
|
164
docs/Testing.md
164
docs/Testing.md
|
@ -1,35 +1,147 @@
|
|||
## 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:
|
||||
1. Event handlers
|
||||
2. Subscription handlers
|
||||
3. View functions
|
||||
For any re-frame app, there's three things to test:
|
||||
|
||||
## 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
|
||||
(defn my-db-handler
|
||||
[db v]
|
||||
(defn select-triangle
|
||||
[db [_ triangle-id]
|
||||
... return a modified version of db)
|
||||
```
|
||||
|
||||
Then, register it in a separate step:
|
||||
Then, register this handler in a separate step:
|
||||
```clj
|
||||
(re-frame.core/reg-event-db
|
||||
:some-id
|
||||
:select-triangle
|
||||
[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`,
|
||||
and then ensure it returns the right (modified version of) `db`.
|
||||
## Event Handlers - Setup - Part 1
|
||||
|
||||
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
|
||||
|
||||
|
@ -74,7 +186,7 @@ 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.
|
||||
That makes `visible-todos` available for direct unit testing.
|
||||
|
||||
## 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.
|
||||
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
|
||||
their keep - they have to deliver a good cost / benefit ratio. And, in my experience
|
||||
with the re-frame architecture, the Reagent view components tend to be an unlikely
|
||||
source of bugs. There's just not much logic in them for me to get wrong.
|
||||
**My Reasons:** every line of code you write is a liability. So tests have
|
||||
to earn their keep by delivering a good cost / benefit ratio.
|
||||
And, in my experience with the re-frame architecture, the View Functions
|
||||
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!!
|
||||
|
||||
|
@ -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
|
||||
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`
|
||||
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
|
||||
use of macros. (Contribute one if you have it).
|
||||
|
||||
|
|
Loading…
Reference in New Issue