Skip to content

Commit 9a10d60

Browse files
committed
[WiP] elmish subscription test FAILING for #57
1 parent a3e0644 commit 9a10d60

File tree

4 files changed

+108
-49
lines changed

4 files changed

+108
-49
lines changed

elmish.md

+58-3
Original file line numberDiff line numberDiff line change
@@ -1883,6 +1883,18 @@ and consider an alternative User Interaction/Experience: Keyboard!
18831883

18841884
#### Use-case: Use Up/Down Keyboard (Arrow) Keys to Increment/Decrement Counter
18851885

1886+
As a user <br />
1887+
I would like to use the keyboard **`[↑]`** (Up) and **`[↓]`** (Down) arrow keys
1888+
to signal the **Increment** and **Decrement** action (_respectively_)
1889+
of the Counter.
1890+
So that I don't have to use a mouse to click a button.
1891+
1892+
![up-down-arrrow-keys](https://user-images.githubusercontent.com/194400/43962720-4cbfb192-9cb0-11e8-9c45-63e7644f1cf6.png)
1893+
1894+
Background reading: https://webaim.org/techniques/keyboard
1895+
1896+
#### Baseline Example Code _Without_ Subscription
1897+
18861898
Let's start by making a "copy" of the code in `/examples/counter-reset`:
18871899
```sh
18881900
mkdir examples/counter-reset-keyboard
@@ -1920,8 +1932,8 @@ function view(model, signal) {
19201932
div(["class=count", "id=count"], [text(model.toString())]), // count
19211933
button(["class=dec", "id=dec", signal('dec')], [text('-')]), // decrement
19221934
button(["class=reset", "id=reset", signal('reset')], [text('Reset')])
1923-
]); // forEach is ES5 so IE9+
1924-
} // yes, for loop is "faster" than forEach, but readability trumps "perf" here!
1935+
]);
1936+
}
19251937

19261938
/* The code block below ONLY Applies to tests run using Node.js */
19271939
/* istanbul ignore else */
@@ -1947,9 +1959,40 @@ should see the Qunit (Broweser) Tests _passing_:
19471959
![counter-reset-keyboard-broweser-tests-passing](https://user-images.githubusercontent.com/194400/43960760-ed098e80-9caa-11e8-9d8f-08310846dacb.png)
19481960

19491961

1950-
19511962
#### How do We _Test_ for Subscription Events?
19521963

1964+
As described above in our "use case" we want to create event listeners,
1965+
for the **`[↑]`** (Up) and **`[↓]`** (Down) arrow keyboard keys,
1966+
so the _only_ way to _test_ for these is to "Trigger" the event(s).
1967+
Thankfully, this is _easy_ in JavaScript. Let's write the test!
1968+
1969+
Add the following _test code_ to your `test/elmish.test.js` file: <br />
1970+
```js
1971+
test here!
1972+
```
1973+
1974+
Run the test (_watch it fail_!):
1975+
```sh
1976+
node test/elmish.test.js
1977+
```
1978+
![subscriptions-test-failing](https://user-images.githubusercontent.com/194400/43964543-f4a6e520-9cb4-11e8-80f5-ae6bb491b83f.png)
1979+
1980+
Hopefully it's clear from reading the test _why_ the assertion is _failing_.
1981+
The counter is not being incremented.
1982+
The last assertion passes because
1983+
"_even a broken clock is right twice a day_" ...
1984+
since the counter is never incremented,
1985+
the count is 0 (zero) throughout the test,
1986+
so the last assertion always passes.
1987+
(_this will not be the case
1988+
once you have the [Up] arrow event listener working_).
1989+
1990+
#### `subscriptions`_Implementation_: Keyboard Keys Increment/Decrement Counter!
1991+
1992+
We could spend an hour googling or I can _give_ you the sample code
1993+
1994+
1995+
19531996

19541997

19551998

@@ -1962,6 +2005,18 @@ for our TodoMVC App!
19622005

19632006
<br />
19642007

2008+
2009+
2010+
2011+
2012+
2013+
2014+
2015+
2016+
2017+
2018+
2019+
19652020
### Why _Not_ use HTML5 `<template>` ??
19662021

19672022
Templates are an _awesome_ feature in HTML5 which

examples/counter-reset-keyboard/counter.js

+25-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* if require is available, it means we are in Node.js Land i.e. testing!
2-
in the broweser, the "elmish" DOM functions are loaded in a <script> tag*/
2+
in the broweser, the "elmish" DOM functions are loaded in a <script> tag */
33
/* istanbul ignore next */
44
if (typeof require !== 'undefined' && this.window !== this) {
5-
var { button, div, empty, h1, mount, text } = require('./elmish.js');
5+
var { button, div, empty, mount, text } = require('../todo-list/elmish.js');
66
}
77

88
function update (action, model) { // Update function takes the current state
@@ -15,18 +15,39 @@ function update (action, model) { // Update function takes the current state
1515
}
1616

1717
function view(model, signal) {
18+
console.log('model:', model);
1819
return div([], [
1920
button(["class=inc", "id=inc", signal('inc')], [text('+')]), // increment
2021
div(["class=count", "id=count"], [text(model.toString())]), // count
2122
button(["class=dec", "id=dec", signal('dec')], [text('-')]), // decrement
2223
button(["class=reset", "id=reset", signal('reset')], [text('Reset')])
23-
]); // forEach is ES5 so IE9+
24-
} // yes, for loop is "faster" than forEach, but readability trumps "perf" here!
24+
]);
25+
}
26+
27+
function subscriptions (signal, root) {
28+
var UP_KEY = 38; // increment the counter when [↑] (up) key is pressed
29+
var DOWN_KEY = 40; // decrement the counter when [↓] (down) key is pressed
30+
console.log('root', root);
31+
root.addEventListener('keyup', function (e) {
32+
console.log('e.keyCode', e.keyCode);
33+
switch (e.keyCode) {
34+
case UP_KEY:
35+
signal('inc')(); // invoke the signal.callback function directly
36+
break;
37+
case DOWN_KEY:
38+
signal('dec')();
39+
break;
40+
default: // do nothing
41+
break;
42+
}
43+
});
44+
}
2545

2646
/* The code block below ONLY Applies to tests run using Node.js */
2747
/* istanbul ignore else */
2848
if (typeof module !== 'undefined' && module.exports) {
2949
module.exports = {
50+
subscriptions: subscriptions,
3051
view: view,
3152
update: update,
3253
}

examples/todo-list/elmish.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,27 @@ function empty (node) {
2222
* @param {Function} subscriptions any event listeners the application needs
2323
*/
2424
function mount (model, update, view, root_element_id, subscriptions) {
25-
var root = document.getElementById(root_element_id); // root DOM element
25+
var ROOT = document.getElementById(root_element_id); // root DOM element
2626
var store_name = 'elmish_' + root_element_id; // test-app !== app
2727

2828
function render (mod, sig, root, subs) { // DRY rendering code (invoked twice)
2929
localStorage.setItem(store_name, JSON.stringify(mod)); // save model!
3030
empty(root); // clear root element (container) before (re)rendering
3131
root.appendChild(view(mod, sig)) // render view based on model & signal
32-
if (subs && typeof subs === 'function') { subs(sig); } // event listeners
32+
if (subs && typeof subs === 'function') { subs(sig, root); } // subscription
3333
}
3434

3535
function signal(action) { // signal function takes action
3636
return function callback() { // and returns callback
3737
console.log('signal action:', action);
3838
model = JSON.parse(localStorage.getItem(store_name)) //|| model;
3939
var updatedModel = update(action, model); // update model for the action
40-
render(updatedModel, signal, root, subscriptions);
40+
render(updatedModel, signal, ROOT, subscriptions);
4141
};
4242
};
4343

4444
model = JSON.parse(localStorage.getItem(store_name)) || model;
45-
render(model, signal, root, subscriptions);
45+
render(model, signal, ROOT, subscriptions);
4646
}
4747

4848
/**

test/elmish.test.js

+21-38
Original file line numberDiff line numberDiff line change
@@ -452,54 +452,37 @@ test('elmish.add_attributes onclick=signal(action) events!', function (t) {
452452

453453

454454
test.only('subscriptions test using counter-reset-keyaboard ⌨️', function (t) {
455-
const { view, update } = require('../examples/counter-reset/counter.js');
456-
455+
const { view, update, subscriptions } =
456+
require('../examples/counter-reset-keyboard/counter.js');
457457
const root = document.getElementById(id);
458-
elmish.mount(0, update, view, id);
459-
// the "model" stored in localStorage should be 7 now:
458+
459+
// mount the counter-reset-keyboard example app WITH subscriptions:
460+
elmish.mount(0, update, view, id, subscriptions);
461+
// keep a "handle" on the count to reduce test length:
462+
const count = document.getElementById('count');
463+
// counter starts off at 0 (zero):
464+
t.equal(parseInt(count.textContent, 10), 0, "Up key press increment 0 -> 1");
460465
t.equal(JSON.parse(localStorage.getItem('elmish_' + id)), 0,
461466
"elmish_store is 0 (as expected). initial state saved to localStorage.");
462467

463-
// trigger the [Enter] keyboard key to ADD the new todo:
464-
document.dispatchEvent(new KeyboardEvent('keypress', {'keyCode': 38})); // ↑
465-
const items = document.querySelectorAll('.view');
466-
467-
// subscription keyCode trigger "branch" test (should NOT fire the signal):
468-
document.dispatchEvent(new KeyboardEvent('keypress', {'keyCode': 40})); // ↓
469-
// t.deepEqual(document.getElementById(id), clone, "#" + id + " no change");
468+
// trigger the [↑] (up) keyboard key to increment the counter:
469+
document.dispatchEvent(new KeyboardEvent('keyup', {'keyCode': 38})); // ↑
470+
t.equal(parseInt(count.textContent, 10), 1, "Up key press increment 0 -> 1");
471+
t.equal(JSON.parse(localStorage.getItem('elmish_' + id)), 1,
472+
"elmish_store 1 (as expected). incremented state saved to localStorage.");
470473

474+
// trigger the [↓] (down) keyboard key to increment the counter:
475+
document.dispatchEvent(new KeyboardEvent('keyup', {'keyCode': 40})); // ↓
476+
t.equal(parseInt(count.textContent, 10), 0, "Up key press dencrement 1 -> 0");
477+
t.equal(JSON.parse(localStorage.getItem('elmish_' + id)), 0,
478+
"elmish_store 0. keyboard down key decrement state saved to localStorage.");
471479

472480
// subscription keyCode trigger "branch" test (should NOT fire the signal):
473481
const clone = document.getElementById(id).cloneNode(true);
474-
document.dispatchEvent(new KeyboardEvent('keypress', {'keyCode': 42})); //
482+
document.dispatchEvent(new KeyboardEvent('keyup', {'keyCode': 42})); //
475483
t.deepEqual(document.getElementById(id), clone, "#" + id + " no change");
476484

477-
478-
// // test that mount still works as expected (check initial state of counter):
479-
// const actual = document.getElementById(id).textContent;
480-
// const actual_stripped = parseInt(actual.replace('+', '')
481-
// .replace('-Reset', ''), 10);
482-
// const expected = 7;
483-
// t.equal(expected, actual_stripped, "Inital state set to 7.");
484-
// // attempting to "re-mount" with a different model value should not work
485-
// // because mount should retrieve the value from localStorage
486-
// elmish.mount(42, update, view, id); // model (42) should be ignored this time!
487-
// t.equal(JSON.parse(localStorage.getItem('elmish_store')), 7,
488-
// "elmish_store is 7 (as expected). initial state saved to localStorage.");
489-
// // increment the counter
490-
// const btn = root.getElementsByClassName("inc")[0]; // click increment button
491-
// btn.click(); // Click the Increment button!
492-
// const state = parseInt(root.getElementsByClassName('count')[0]
493-
// .textContent, 10);
494-
// t.equal(state, 8, "State is 8 after increment.");
495-
// // the "model" stored in localStorage should also be 8 now:
496-
// t.equal(JSON.parse(localStorage.getItem('elmish_store')), 8,
497-
// "elmish_store is 8 (as expected).");
498-
// elmish.empty(root); // reset the DOM to simulate refreshing a browser window
499-
// elmish.mount(5, update, view, id); // 5 ignored! read model from localStorage
500-
// // clearing DOM does NOT clear the localStorage (this is desired behaviour!)
501-
// t.equal(JSON.parse(localStorage.getItem('elmish_store')), 8,
502-
// "elmish_store still 8 from increment (above) saved in localStorage");
503485
localStorage.removeItem('elmish_store');
486+
elmish.empty(root);
504487
t.end()
505488
});

0 commit comments

Comments
 (0)