javascript-todo-list-tutorial
Version:
Learn how to build a Todo List in JavaScript following Test Driven Development TDD!
681 lines (605 loc) • 29 kB
JavaScript
const test = require('tape'); // https://github.com/dwyl/learn-tape
const fs = require('fs'); // to read html files (see below)
const path = require('path'); // so we can open files cross-platform
const html = fs.readFileSync(path.resolve(__dirname, '../index.html'));
require('jsdom-global')(html); // https://github.com/rstacruz/jsdom-global
const app = require('../lib/todo-app.js'); // functions to test
const id = 'test-app'; // all tests use 'test-app' as root element
const elmish = require('../lib/elmish.js'); // import "elmish" core functions
test('`model` (Object) has desired keys', function (t) {
const keys = Object.keys(app.model);
t.deepEqual(keys, ['todos', 'hash'], "`todos` and `hash` keys are present.");
t.true(Array.isArray(app.model.todos), "model.todos is an Array")
t.end();
});
test('`update` default case should return model unmodified', function (t) {
const model = JSON.parse(JSON.stringify(app.model));
const unmodified_model = app.update('UNKNOWN_ACTION', model);
t.deepEqual(model, unmodified_model, "model returned unmodified");
t.end();
});
test('update `ADD` a new todo item to model.todos Array', function (t) {
const model = JSON.parse(JSON.stringify(app.model)); // initial state
t.equal(model.todos.length, 0, "initial model.todos.length is 0");
const updated_model = app.update('ADD', model, "Add Todo List Item");
const expected = { id: 1, title: "Add Todo List Item", done: false };
t.equal(updated_model.todos.length, 1, "updated_model.todos.length is 1");
t.deepEqual(updated_model.todos[0], expected, "Todo list item added.");
t.end();
});
test('update `TOGGLE` a todo item from done=false to done=true', function (t) {
const model = JSON.parse(JSON.stringify(app.model)); // initial state
const model_with_todo = app.update('ADD', model, "Toggle a todo list item");
const item = model_with_todo.todos[0];
const model_todo_done = app.update('TOGGLE', model_with_todo, item.id);
const expected = { id: 1, title: "Toggle a todo list item", done: true };
t.deepEqual(model_todo_done.todos[0], expected, "Todo list item Toggled.");
t.end();
});
test('`TOGGLE` (undo) a todo item from done=true to done=false', function (t) {
const model = JSON.parse(JSON.stringify(app.model)); // initial state
const model_with_todo = app.update('ADD', model, "Toggle a todo list item");
const item = model_with_todo.todos[0];
const model_todo_done = app.update('TOGGLE', model_with_todo, item.id);
const expected = { id: 1, title: "Toggle a todo list item", done: true };
t.deepEqual(model_todo_done.todos[0], expected, "Toggled done=false >> true");
// add another item before "undoing" the original one:
const model_second_item = app.update('ADD', model_todo_done, "Another todo");
t.equal(model_second_item.todos.length, 2, "there are TWO todo items");
// Toggle the original item such that: done=true >> done=false
const model_todo_undone = app.update('TOGGLE', model_second_item, item.id);
const undone = { id: 1, title: "Toggle a todo list item", done: false };
t.deepEqual(model_todo_undone.todos[0],undone, "Todo item Toggled > undone!");
t.end();
});
// this is used for testing view functions which require a signal function
function mock_signal () {
return function inner_function() {
console.log('done');
}
}
test('render_item HTML for a single Todo Item', function (t) {
const model = {
todos: [
{ id: 1, title: "Learn Elm Architecture", done: true },
],
hash: '#/' // the "route" to display
};
// render the ONE todo list item:
document.getElementById(id).appendChild(
app.render_item(model.todos[0], model, mock_signal),
);
const done = document.querySelectorAll('.completed')[0].textContent;
t.equal(done, 'Learn Elm Architecture', 'Done: Learn "TEA"');
const checked = document.querySelectorAll('input')[0].checked;
t.equal(checked, true, 'Done: ' + model.todos[0].title + " is done=true");
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
t.end();
});
test('render_item HTML without a valid signal function', function (t) {
const model = {
todos: [
{ id: 1, title: "Learn Elm Architecture", done: true },
],
hash: '#/' // the "route" to display
};
// render the ONE todo list item:
document.getElementById(id).appendChild(
app.render_item(model.todos[0], model),
);
const done = document.querySelectorAll('.completed')[0].textContent;
t.equal(done, 'Learn Elm Architecture', 'Done: Learn "TEA"');
const checked = document.querySelectorAll('input')[0].checked;
t.equal(checked, true, 'Done: ' + model.todos[0].title + " is done=true");
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
t.end();
});
test('render_main "main" view using (elmish) HTML DOM functions', function (t) {
const model = {
todos: [
{ id: 1, title: "Learn Elm Architecture", done: true },
{ id: 2, title: "Build Todo List App", done: false },
{ id: 3, title: "Win the Internet!", done: false }
],
hash: '#/' // the "route" to display
};
// render the "main" view and append it to the DOM inside the `test-app` node:
document.getElementById(id).appendChild(app.render_main(model, mock_signal));
// test that the title text in the model.todos was rendered to <label> nodes:
document.querySelectorAll('.view').forEach(function (item, index) {
t.equal(item.textContent, model.todos[index].title,
"index #" + index + " <label> text: " + item.textContent)
})
const inputs = document.querySelectorAll('input'); // todo items are 1,2,3
[true, false, false].forEach(function(state, index){
t.equal(inputs[index + 1].checked, state,
"Todo #" + index + " is done=" + state)
})
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
t.end();
});
test('render_footer view using (elmish) HTML DOM functions', function (t) {
const model = {
todos: [
{ id: 1, title: "Learn Elm Architecture", done: true },
{ id: 2, title: "Build Todo List App", done: false },
{ id: 3, title: "Win the Internet!", done: false }
],
hash: '#/' // the "route" to display
};
// render_footer view and append it to the DOM inside the `test-app` node:
document.getElementById(id).appendChild(app.render_footer(model));
// todo-count should display 2 items left (still to be done):
const left = document.getElementById('count').innerHTML;
t.equal(left, "<strong>2</strong> items left", "Todos remaining: " + left);
// count number of footer <li> items:
t.equal(document.querySelectorAll('li').length, 3, "3 <li> in <footer>");
// check footer link text and href:
const link_text = ['All', 'Active', 'Completed'];
const hrefs = ['#/', '#/active', '#/completed'];
document.querySelectorAll('a').forEach(function (a, index) {
// check link text:
t.equal(a.textContent, link_text[index], "<footer> link #" + index
+ " is: " + a.textContent + " === " + link_text[index]);
// check hrefs:
t.equal(a.href.replace('about:blank', ''), hrefs[index],
"<footer> link #" + index + " href is: " + hrefs[index]);
});
// check for "Clear completed" button in footer:
const clear = document.querySelectorAll('.clear-completed')[0].textContent;
t.equal(clear, 'Clear completed [1]',
'<button> in <footer> "Clear completed [1]"');
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
t.end();
});
test('render_footer 1 item left (pluarisation test)', function (t) {
const model = {
todos: [
{ id: 1, title: "Be excellent to each other!", done: false }
],
hash: '#/' // the "route" to display
};
// render_footer view and append it to the DOM inside the `test-app` node:
document.getElementById(id).appendChild(app.render_footer(model));
// todo-count should display "1 item left" (still to be done):
const left = document.getElementById('count').innerHTML;
t.equal(left, "<strong>1</strong> item left", "Todos remaining: " + left);
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
t.end();
});
test('view renders the whole todo app using "partials"', function (t) {
// render the view and append it to the DOM inside the `test-app` node:
document.getElementById(id).appendChild(app.view(app.model)); // initial_model
t.equal(document.querySelectorAll('h1')[0].textContent, "todos", "<h1>todos");
// placeholder:
const placeholder = document.getElementById('new-todo')
.getAttribute("placeholder");
t.equal(placeholder, "What needs to be done?", "paceholder set on <input>");
// todo-count should display 0 items left (based on initial_model):
const left = document.getElementById('count').innerHTML;
t.equal(left, "<strong>0</strong> items left", "Todos remaining: " + left);
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
t.end();
});
test('1. No Todos, should hide #footer and #main', function (t) {
// render the view and append it to the DOM inside the `test-app` node:
document.getElementById(id).appendChild(app.view({todos: []})); // No Todos
const main_display = window.getComputedStyle(document.getElementById('main'));
t.equal(main_display._values.display, 'none', "No Todos, hide #main");
const main_footer= window.getComputedStyle(document.getElementById('footer'));
t.equal(main_footer._values.display, 'none', "No Todos, hide #footer");
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
t.end();
});
// Testing localStorage requires "polyfil" because:a
// https://github.com/jsdom/jsdom/issues/1137 ¯\_(ツ)_/¯
// globals are usually bad! but a "necessary evil" here.
global.localStorage = global.localStorage ? global.localStorage : {
getItem: function(key) {
const value = this[key];
return typeof value === 'undefined' ? null : value;
},
setItem: function (key, value) {
this[key] = value;
},
removeItem: function (key) {
delete this[key]
}
}
localStorage.removeItem('todos-elmish_store');
test('2. New Todo, should allow me to add todo items', function (t) {
elmish.empty(document.getElementById(id));
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount({todos: []}, app.update, app.view, id, app.subscriptions);
const new_todo = document.getElementById('new-todo');
// "type" content in the <input id="new-todo">:
const todo_text = 'Make Everything Awesome! '; // deliberate whitespace!
new_todo.value = todo_text;
// trigger the [Enter] keyboard key to ADD the new todo:
document.dispatchEvent(new KeyboardEvent('keyup', {'keyCode': 13}));
const items = document.querySelectorAll('.view');
t.equal(items.length, 1, "should allow me to add todo items");
// check if the new todo was added to the DOM:
const actual = document.getElementById('1').textContent;
t.equal(todo_text.trim(), actual, "should trim text input")
// subscription keyCode trigger "branch" test (should NOT fire the signal):
const clone = document.getElementById(id).cloneNode(true);
document.dispatchEvent(new KeyboardEvent('keyup', {'keyCode': 42}));
t.deepEqual(document.getElementById(id), clone, "#" + id + " no change");
// check that the <input id="new-todo"> was reset after the new item was added
t.equal(new_todo.value, '',
"should clear text input field when an item is added")
const main_display = window.getComputedStyle(document.getElementById('main'));
t.equal('block', main_display._values.display,
"should show #main and #footer when items added");
const main_footer= window.getComputedStyle(document.getElementById('footer'));
t.equal('block', main_footer._values.display, "item added, show #footer");
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
localStorage.removeItem('todos-elmish_' + id);
t.end();
});
test('3. Mark all as completed ("TOGGLE_ALL")', function (t) {
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
const model = {
todos: [
{ id: 0, title: "Learn Elm Architecture", done: true },
{ id: 1, title: "Build Todo List App", done: false },
{ id: 2, title: "Win the Internet!", done: false }
],
hash: '#/' // the "route" to display
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
// confirm that the ONLY the first todo item is done=true:
const items = document.querySelectorAll('.view');
document.querySelectorAll('.toggle').forEach(function(item, index) {
t.equal(item.checked, model.todos[index].done,
"Todo #" + index + " is done=" + item.checked
+ " text: " + items[index].textContent)
})
// click the toggle-all checkbox to trigger TOGGLE_ALL: >> true
document.getElementById('toggle-all').click(); // click toggle-all checkbox
document.querySelectorAll('.toggle').forEach(function(item, index) {
t.equal(item.checked, true,
"TOGGLE each Todo #" + index + " is done=" + item.checked
+ " text: " + items[index].textContent)
});
t.equal(document.getElementById('toggle-all').checked, true,
"should allow me to mark all items as completed")
// click the toggle-all checkbox to TOGGLE_ALL (again!) true >> false
document.getElementById('toggle-all').click(); // click toggle-all checkbox
document.querySelectorAll('.toggle').forEach(function(item, index) {
t.equal(item.checked, false,
"TOGGLE_ALL Todo #" + index + " is done=" + item.checked
+ " text: " + items[index].textContent)
})
t.equal(document.getElementById('toggle-all').checked, false,
"should allow me to clear the completion state of all items")
// *manually* "click" each todo item:
document.querySelectorAll('.toggle').forEach(function(item, index) {
item.click(); // this should "toggle" the todo checkbox to done=true
t.equal(item.checked, true,
".toggle.click() (each) Todo #" + index + " which is done=" + item.checked
+ " text: " + items[index].textContent)
});
// the toggle-all checkbox should be "checked" as all todos are done=true!
t.equal(document.getElementById('toggle-all').checked, true,
"complete all checkbox should update state when items are completed")
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
localStorage.removeItem('todos-elmish_store');
t.end();
});
test('4. Item: should allow me to mark items as complete', function (t) {
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false }
],
hash: '#/' // the "route" to display
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
const item = document.getElementById('0')
t.equal(item.textContent, model.todos[0].title, 'Item contained in model.');
// confirm that the todo item is NOT done (done=false):
t.equal(document.querySelectorAll('.toggle')[0].checked, false,
'Item starts out "active" (done=false)');
// click the checkbox to toggle it to done=true
document.querySelectorAll('.toggle')[0].click()
t.equal(document.querySelectorAll('.toggle')[0].checked, true,
'Item should allow me to mark items as complete');
// click the checkbox to toggle it to done=false "undo"
document.querySelectorAll('.toggle')[0].click()
t.equal(document.querySelectorAll('.toggle')[0].checked, false,
'Item should allow me to un-mark items as complete');
t.end();
});
test('4.1 DELETE item by clicking <button class="destroy">', function (t) {
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false }
],
hash: '#/' // the "route" to display
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
// const todo_count = ;
t.equal(document.querySelectorAll('.destroy').length, 1, "one destroy button")
const item = document.getElementById('0')
t.equal(item.textContent, model.todos[0].title, 'Item contained in DOM.');
// DELETE the item by clicking on the <button class="destroy">:
const button = item.querySelectorAll('button.destroy')[0];
button.click()
// confirm that there is no loger a <button class="destroy">
t.equal(document.querySelectorAll('button.destroy').length, 0,
'there is no loger a <button class="destroy"> as the only item was DELETEd')
t.equal(document.getElementById('0'), null, 'todo item successfully DELETEd');
t.end();
});
test('5.1 Editing: > Render an item in "editing mode"', function (t) {
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false },
{ id: 1, title: "Bootstrap for as long as you can", done: false },
{ id: 2, title: "Let's solve our own problem", done: false }
],
hash: '#/', // the "route" to display
editing: 2 // edit the 3rd todo list item (which has id == 2)
};
// render the ONE todo list item in "editing mode" based on model.editing:
document.getElementById(id).appendChild(
app.render_item(model.todos[2], model, mock_signal),
);
// test that signal (in case of the test mock_signal) is onclick attribute:
t.equal(document.querySelectorAll('.view > label')[0].onclick.toString(),
mock_signal().toString(), "mock_signal is onclick attribute of label");
// test that the <li class="editing"> and <input class="edit"> was rendered:
t.equal(document.querySelectorAll('.editing').length, 1,
"<li class='editing'> element is visible");
t.equal(document.querySelectorAll('.edit').length, 1,
"<input class='edit'> element is visible");
t.equal(document.querySelectorAll('.edit')[0].value, model.todos[2].title,
"<input class='edit'> has value: " + model.todos[2].title);
t.end();
});
test('5.2 Double-click an item <label> to edit it', function (t) {
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false },
{ id: 1, title: "Let's solve our own problem", done: false }
],
hash: '#/' // the "route" to display
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
const label = document.querySelectorAll('.view > label')[1]
// "double-click" i.e. click the <label> twice in quick succession:
label.click();
label.click();
// confirm that we are now in editing mode:
t.equal(document.querySelectorAll('.editing').length, 1,
"<li class='editing'> element is visible");
t.equal(document.querySelectorAll('.edit')[0].value, model.todos[1].title,
"<input class='edit'> has value: " + model.todos[1].title);
t.end();
});
test('5.2.2 Slow clicks do not count as double-click > no edit!', function (t) {
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false },
{ id: 1, title: "Let's solve our own problem", done: false }
],
hash: '#/' // the "route" to display
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
const label = document.querySelectorAll('.view > label')[1]
// "double-click" i.e. click the <label> twice in quick succession:
label.click();
setTimeout(function (){
label.click();
// confirm that we are now in editing mode:
t.equal(document.querySelectorAll('.editing').length, 0,
"<li class='editing'> element is NOT visible");
t.end();
}, 301)
});
test('5.3 [ENTER] Key in edit mode triggers SAVE action', function (t) {
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false },
{ id: 1, title: "Let's solve our own problem", done: false }
],
hash: '#/', // the "route" to display
editing: 1 // edit the 3rd todo list item (which has id == 2)
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
// change the
const updated_title = "Do things that don\'t scale! "
// apply the updated_title to the <input class="edit">:
document.querySelectorAll('.edit')[0].value = updated_title;
// trigger the [Enter] keyboard key to ADD the new todo:
document.dispatchEvent(new KeyboardEvent('keyup', {'keyCode': 13}));
// confirm that the todo item title was updated to the updated_title:
const label = document.querySelectorAll('.view > label')[1].textContent;
t.equal(label, updated_title.trim(),
"item title updated to:" + updated_title + ' (trimmed)');
t.end();
});
test('5.4 SAVE should remove the item if an empty text string was entered',
function (t) {
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false },
{ id: 1, title: "Let's solve our own problem", done: false }
],
hash: '#/', // the "route" to display
editing: 1 // edit the 3rd todo list item (which has id == 2)
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
t.equal(document.querySelectorAll('.view').length, 2, 'todo count: 2');
// apply empty string to the <input class="edit">:
document.querySelectorAll('.edit')[0].value = '';
// trigger the [Enter] keyboard key to ADD the new todo:
document.dispatchEvent(new KeyboardEvent('keyup', {'keyCode': 13}));
// confirm that the todo item was removed!
t.equal(document.querySelectorAll('.view').length, 1, 'todo count: 1');
t.end();
});
test('5.5 CANCEL should cancel edits on escape', function (t) {
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false },
{ id: 1, title: "Let's solve our own problem", done: false }
],
hash: '#/', // the "route" to display
editing: 1 // edit the 3rd todo list item (which has id == 2)
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
t.equal(document.querySelectorAll('.view > label')[1].textContent,
model.todos[1].title, 'todo id 1 has title: ' + model.todos[1].title);
// apply empty string to the <input class="edit">:
document.querySelectorAll('.edit')[0].value = 'Hello World';
// trigger the [esc] keyboard key to CANCEL editing
document.dispatchEvent(new KeyboardEvent('keyup', {'keyCode': 27}));
// confirm the item.title is still the original title:
t.equal(document.querySelectorAll('.view > label')[1].textContent,
model.todos[1].title, 'todo id 1 has title: ' + model.todos[1].title);
localStorage.removeItem('todos-elmish_' + id);
t.end();
});
test('6. Counter > should display the current number of todo items',
function (t) {
elmish.empty(document.getElementById(id));
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false },
{ id: 1, title: "Bootstrap for as long as you can", done: false },
{ id: 2, title: "Let's solve our own problem", done: false }
],
hash: '#/'
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
// count:
const count = parseInt(document.getElementById('count').textContent, 10);
t.equal(count, model.todos.length, "displays todo item count: " + count);
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
localStorage.removeItem('todos-elmish_' + id);
t.end();
});
test('7. Clear Completed > should display the number of completed items',
function (t) {
elmish.empty(document.getElementById(id));
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false },
{ id: 1, title: "Bootstrap for as long as you can", done: true },
{ id: 2, title: "Let's solve our own problem", done: true }
],
hash: '#/'
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
// count todo items in DOM:
t.equal(document.querySelectorAll('.view').length, 3,
"at the start, there are 3 todo items in the DOM.");
// count completed items
const completed_count =
parseInt(document.getElementById('completed-count').textContent, 10);
const done_count = model.todos.filter(function(i) {return i.done }).length;
t.equal(completed_count, done_count,
"displays completed items count: " + completed_count);
// clear completed items:
const button = document.querySelectorAll('.clear-completed')[0];
button.click();
// confirm that there is now only ONE todo list item in the DOM:
t.equal(document.querySelectorAll('.view').length, 1,
"after clearing completed items, there is only 1 todo item in the DOM.");
// no clear completed button in the DOM when there are no "done" todo items:
t.equal(document.querySelectorAll('clear-completed').length, 0,
'no clear-completed button when there are no done items.')
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
localStorage.removeItem('todos-elmish_' + id);
t.end();
});
test('8. Persistence > should persist its data', function (t) {
elmish.empty(document.getElementById(id));
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false }
],
hash: '#/'
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
// confirm that the model is saved to localStorage
// console.log('localStorage', localStorage.getItem('todos-elmish_' + id));
t.equal(localStorage.getItem('todos-elmish_' + id),
JSON.stringify(model), "data is persisted to localStorage");
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
localStorage.removeItem('todos-elmish_' + id);
t.end();
});
test('9. Routing > should allow me to display active/completed/all items',
function (t) {
localStorage.removeItem('todos-elmish_' + id);
elmish.empty(document.getElementById(id));
const model = {
todos: [
{ id: 0, title: "Make something people want.", done: false },
{ id: 1, title: "Bootstrap for as long as you can", done: true },
{ id: 2, title: "Let's solve our own problem", done: true }
],
hash: '#/active' // ONLY ACTIVE items
};
// render the view and append it to the DOM inside the `test-app` node:
elmish.mount(model, app.update, app.view, id, app.subscriptions);
const mod = app.update('ROUTE', model);
// t.equal(mod.hash, '#/', 'default route is #/');
t.equal(document.querySelectorAll('.view').length, 1, "one active item");
let selected = document.querySelectorAll('.selected')[0]
t.equal(selected.id, 'active', "active footer filter is selected");
// empty:
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
// show COMPLTED items:
model.hash = '#/completed';
elmish.mount(model, app.update, app.view, id, app.subscriptions);
t.equal(document.querySelectorAll('.view').length, 2,
"two completed items");
selected = document.querySelectorAll('.selected')[0]
t.equal(selected.id, 'completed', "completed footer filter is selected");
// empty:
elmish.empty(document.getElementById(id));
localStorage.removeItem('todos-elmish_' + id);
// show ALL items:
model.hash = '#/';
elmish.mount(model, app.update, app.view, id, app.subscriptions);
t.equal(document.querySelectorAll('.view').length, 3,
"three items total");
selected = document.querySelectorAll('.selected')[0]
t.equal(selected.id, 'all', "all footer filter is selected");
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
localStorage.removeItem('todos-elmish_' + id);
t.end();
});