diff --git a/README.md b/README.md
index 71e51ef4..e615f705 100644
--- a/README.md
+++ b/README.md
@@ -52,15 +52,15 @@ very easy!
[![everybodys-gotta-learn-sometime](https://cloud.githubusercontent.com/assets/194400/25806590/a1619644-33fb-11e7-8b84-1a21be188fb7.png)](https://www.youtube.com/results?q=The+Korgis+-+Everybody%27s+Gotta+Learn+Sometime)
Anyone who knows a _little_ bit of JavaScript
-and wants to learn how to organize/structure
-their code in the most _sane_ and easy to understand way.
+and wants to learn how to organize/structure
+their code/app in the most _sane_, predictable and testable way.
### _Prerequisites_?
![all-you-need-is-less](https://cloud.githubusercontent.com/assets/194400/25772135/a4230490-325b-11e7-9f12-da19fa4eb5e9.png)
+ _Basic_ JavaScript Knowledge.
-see: https://github.com/iteles/Javascript-the-Good-Parts-notes
+see: [github.com/iteles/**Javascript**-the-**Good-Parts**-notes](https://github.com/iteles/Javascript-the-Good-Parts-notes)
+ _Basic_ Understanding of TDD. If you are _completely_ new to TDD,
please see: https://github.com/dwyl/learn-tdd
+ A computer
@@ -86,7 +86,7 @@ Start with a few definitions:
![elm-muv-architecture-diagram](https://cloud.githubusercontent.com/assets/194400/25773775/b6a4b850-327b-11e7-9857-79b6972b49c3.png)
Don't worry if you don't understand this diagram (_yet_),
-it will all become clear when you start seeing it in _action_!
+it will all become clear when you start seeing it in _action_ (_below_)!
## _How?_
@@ -103,7 +103,6 @@ When you open `examples/counter-basic/index.html` you should see:
![elm-architecture-counter](https://cloud.githubusercontent.com/assets/194400/25780607/d2251eac-3321-11e7-8e65-9abbfa204fb3.gif)
-
Try clicking on the buttons to increase/decrease the counter.
### 3. Edit Some Code!
@@ -221,9 +220,11 @@ e.g:
### 7. Read the _Tests_!
-In the _first_ example we kept everything in one file (`index.html`)
-for simplicity. In order to write tests, we need to _split_ out the
-JavaScript code from the HTML.
+In the _first_ example we kept everything in
+_one_ file (`index.html`) for simplicity.
+In order to write tests (_and collect coverage_),
+we need to _separate_ out
+the JavaScript code from the HTML.
For this example there are 3 _separate_ files:
@@ -234,7 +235,7 @@ Let's start by opening the `/examples/counter-basic-test/index.html`
file in a web browser:
http://127.0.0.1:8000/examples/counter-basic-test/?coverage
-![counter-tests](https://cloud.githubusercontent.com/assets/194400/25776602/9df4b550-32ba-11e7-958b-25baaeeea212.png)
+![counter-coverage](https://cloud.githubusercontent.com/assets/194400/25816673/b994d25a-341c-11e7-8fd1-52e136fb7152.png)
Because all functions are "pure" testing
the `update` function is _very_ easy:
@@ -257,8 +258,8 @@ test('Test Update decrement: update(3, "dec") returns 2', function(assert) {
```
open: `examples/counter-basic-test/test.js` to see these and _other_ tests.
-> The _reason_ why Apps built using the Elm Architecture are _**so
-easy**_ to _understand_
+> The _reason_ why Apps built using the Elm Architecture
+are _**so easy**_ to _understand_
(_or ["**reason about**"](http://stackoverflow.com/q/18666821)_)
and _test_ is that all functions are "Pure".
@@ -308,15 +309,96 @@ console.log(increment(increment(increment(counter)))); // 3
```
see: https://repl.it/FIpV
+#### 8.3 Counter Example written in "Impure" JS
+
+It's _easy_ to get
+[_suckered_](http://www.urbandictionary.com/define.php?term=suckered)
+into thinking that the "_impure_" version of the counter
+`examples/counter-basic-impure/index.html`
+is "_simpler_" ...
+the _complete_ code (_including HTML and JS_) is ***8 lines***:
+
+```html
+
+
0
+
+
+```
+
+
+This counter _does_ the same thing as
+our Elm Architecture example (_above_),
+and to the _end-user_ the UI **_looks_ identical**:
+
+![counter-impure-665](https://cloud.githubusercontent.com/assets/194400/25816521/3a0e0722-341c-11e7-9afc-269abb4bb225.png)
+
+
+The difference is that in the _impure_ example is "_mutating state_"
+and it's impossible to predict what that state will be!
+
+> _Annoyingly, for the person explaining the benefits
+of function "purity" and the virtues of the Elm Architecture
+the "impure" example is both **fewer lines of code**
+(which means it **loads faster**!), takes less time to read
+and renders faster because only the `
` text content
+is being updated on each update!
+> This is why it can often be **difficult to explain** to "**non-technical**"
+**people** that code which has similar output
+on the **screen**(s)
+might **not the same** quality "**behind the scenes**"!_
+> _Writing impure functions is like setting off on a marathon run after
+[tying your shoelaces **incorrectly**](https://youtu.be/zAFcV7zuUDA) ...
+You might be "OK" for a while, but pretty soon your laces will come undone
+and you will have to **stop** and **re-do** them._
+
+
To conclude: Pure functions doe not mutate a "global" state
-and are thus predictable and thus easy to test;
+and are thus predictable and easy to test;
we _always_ use "Pure" functions in Apps built with the Elm Architecture.
The moment you use "_impure_" functions you forfeit reliability.
-### 9. Extend the Counter Example following "TDD"
+### 9. Extend the Counter Example following "TDD": Reset the Count!
+
+As you (_hopefully_) recall from our
+[Step-by-Step TDD Tutorial](https://github.com/dwyl/learn-tdd),
+when we craft code following the "TDD" approach,
+we go through the following steps:
+1. Read and understand the "user story" (_e.g: [issues/5](https://github.com/dwyl/learn-elm-architecture-in-javascript/issues/5) in this case_)
+![reset-counter-user-story](https://cloud.githubusercontent.com/assets/194400/25817522/84fdd9bc-341f-11e7-9efd-406d76a3b1f3.png)
+2. Make sure the "_acceptance criteria_" are clear
+(_the checklist in the issue_)
+3. Write your test(s) based on the acceptance criteria.
+(_Tip: a single feature - in this case resetting the counter - can
+ and often `should` have multiple tests to cover all cases._)
+4. Write code to make the test(s) pass.
+
+#### 9.1 Tests for Resetting the Counter
+
+We _always_ start with the Model test(s)
+(_because they are the easiest_):
+
+```js
+test('Test: reset counter returns 0', function(assert) {
+ var result = update(6, "res");
+ assert.equal(result, 0);
+});
+```
+#### 9.2 Watch it Fail!
+
+Watch the test _fail_ in your Web Browser:
+![reset-counter-failing-test](https://cloud.githubusercontent.com/assets/194400/25818566/e2c33152-3422-11e7-9c4c-9ecd9fa9ffc6.png)
+
+#### 9.3 Make it Pass (_writing the minimum code_)
-As you (_hopefully_) recall from our TDD Tutorial
+In the case of an App written with the Elm Architecture,
+the minimum code is:
++ Action
++ Update (_case and/or function_)
@@ -389,7 +471,9 @@ to build something full-featured and easy/fast to read!!
If you can build with "ES5" JavaScript:
a) you side-step the
[_noise_](https://twitter.com/iamdevloper/status/610191865216786432)
-and focus on core skills that _already_ work everywhere!
+and focus on core skills that _already_ work everywhere!
+(_don't worry you can always "top-up" your
+JS knowledge later with ES6, etc!)
b) you don't need to waste time installing
[_**Two Hundred Megabytes**_](https://cloud.githubusercontent.com/assets/194400/13321493/39fcfa30-dbc7-11e5-8b05-f046675f9cb6.png)
of dependencies just to run a simple project!
diff --git a/examples/counter-reset/counter.js b/examples/counter-reset/counter.js
new file mode 100644
index 00000000..77edeeda
--- /dev/null
+++ b/examples/counter-reset/counter.js
@@ -0,0 +1,65 @@
+// Mount Function receives all the elements and mounts the app
+function mount(muv, id) { // state is encapsulated by mount function
+ var root = document.getElementById(id);
+ var update = muv.update; // make local copies of the init parameters
+ var state = muv.model; // initial state
+ var view = muv.view; // view is what renders the UI in Browser
+
+ function signal(action) { // signal function takes action
+ return function callback() { // and returns callback
+ state = update(state, action); // update state according to action
+ view(signal, state, root); // subsequent re-rendering
+ };
+ };
+ view(signal, state, root); // render initial state (once)
+}
+// Define the Component's Actions:
+var Inc = 'inc'; // increment the counter
+var Dec = 'dec'; // decrement the counter
+
+
+function update(model, action) { // Update function takes the current state
+ switch(action) { // and an action (String) runs a switch
+ case Inc: return model + 1; // add 1 to the model
+ case Dec: return model - 1; // subtract 1 from model
+ default: return model; // if no action, return curent state.
+ } // (default action always returns current)
+}
+
+function view(signal, model, root) {
+ empty(root); // clear root element before
+ return [ // Store DOM nodes in an array
+ button('+', signal, Inc), // then iterate to append them
+ div('count', model), // avoids repetition.
+ button('-', signal, Dec)
+ ].forEach(function(el){ root.appendChild(el) }); // forEach is ES5 so IE9+
+} // yes, for loop is "faster" than forEach, but readability trumps "perf" here!
+
+// The following are "Helper" Functions which each "Do ONLY One Thing" and are
+// used in the "View" function to render the Model (State) to the Browser DOM:
+
+// empty the contents of a given DOM element "node" (before re-rendering)
+function empty(node) {
+ while (node.firstChild) {
+ node.removeChild(node.firstChild);
+ }
+} // Inspired by: stackoverflow.com/a/3955238/1148249
+
+function button(text, signal, action) {
+ var button = document.createElement('button');
+ var text = document.createTextNode(text); // human-readable button text
+ button.appendChild(text); // text goes *inside* not attrib
+ button.className = action; // use action as CSS class
+ button.onclick = signal(action); // onclick tells how to process
+ return button; // return the DOM node(s)
+} // how to create a button in JavaScript: stackoverflow.com/a/8650996/1148249
+
+function div(divid, text) {
+ var div = document.createElement('div');
+ div.id = divid;
+ if(text !== undefined) { // if text is passed in render it in a "Text Node"
+ var txt = document.createTextNode(text);
+ div.appendChild(txt);
+ }
+ return div;
+}
diff --git a/examples/counter-reset/index.html b/examples/counter-reset/index.html
new file mode 100644
index 00000000..3833a776
--- /dev/null
+++ b/examples/counter-reset/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Elm Architecture in JS - Counter Reset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/counter-reset/test.js b/examples/counter-reset/test.js
new file mode 100644
index 00000000..707b3a7d
--- /dev/null
+++ b/examples/counter-reset/test.js
@@ -0,0 +1,54 @@
+var id = 'test-app'
+
+test('Test Update update(0) returns 0 (current state)', function(assert) {
+ var result = update(0);
+ assert.equal(result, 0);
+});
+
+test('Test Update increment: update(1, "inc") returns 2', function(assert) {
+ var result = update(1, "inc");
+ assert.equal(result, 2);
+});
+
+test('Test Update decrement: update(3, "dec") returns 2', function(assert) {
+ var result = update(1, "dec");
+ assert.equal(result, 0);
+});
+
+test('Test negative state: update(-9, "inc") returns -8', function(assert) {
+ var result = update(-9, "inc");
+ assert.equal(result, -8);
+});
+
+test('mount({model: 7, update: update, view: view}, "'
+ + id +'") sets initial state to 7', function(assert) {
+ var init = {model: 7, update: update, view: view};
+ mount(init, id);
+ var state = document.getElementById(id).textContent.replace(/-+/, '');
+ assert.equal(state, 7);
+});
+
+test('empty("test-app") should clear DOM in root node', function(assert) {
+ empty(document.getElementById(id));
+ var init = {model: 7, update: update, view: view};
+ mount(init, id);
+ empty(document.getElementById(id));
+ var result = document.getElementById(id).innerHtml
+ assert.equal(result, undefined);
+});
+
+test('click on "+" button to re-render state (increment model by 1)',
+function(assert) {
+ document.body.appendChild(div(id));
+ var init = {model: 7, update: update, view: view};
+ mount(init, id);
+ document.getElementsByTagName('button')[2].click(); // there are 4 buttons
+ var state = document.getElementById(id).textContent.replace(/-+/, '');
+ assert.equal(state, 8); // model was incremented successfully
+ empty(document.getElementById(id)); // clean up after tests
+});
+
+test('Test reset counter when model/state is 6 returns 0', function(assert) {
+ var result = update(6, "reset");
+ assert.equal(result, 0);
+});
diff --git a/examples/style.css b/examples/style.css
index 73290445..1cde742d 100644
--- a/examples/style.css
+++ b/examples/style.css
@@ -15,6 +15,6 @@ button {
background-color:#e74c3c; border-color: #c0392b;
}
-#qunit-header {
- font-size: 0.5em !important;
+#qunit-header { /* just cause the default style makes the header HUGE!! */
+ font-size: 0.4em !important;
}