Delete All Completed Todos From the List

Story: As a user, I want to be able to delete all completed todos from my todos list.

TodoMVC actions

It should create an action to delete all completed todos

Write a test.

diff --git a/tests/todomvc/actions.spec.js b/tests/todomvc/actions.spec.js
index 2ae2dec..6ac3464 100644
--- a/tests/todomvc/actions.spec.js
+++ b/tests/todomvc/actions.spec.js
@@ -45,5 +45,13 @@ describe('TodoMVC actions', () => {
     };

     expect(todomvc.actions.toggleCompleteAllTodos()).to.deep.equal(expectedAction)
+  });
+
+  it('Should create an action to delete all completed todos', () => {
+    const expectedAction = {
+      type: todomvc.types.DELETE_COMPLETED
+    };
+
+    expect(todomvc.actions.deleteCompletedTodos()).to.deep.equal(expectedAction)
   })
 });
(END)

Run the test and watch it fail.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    1) Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo


  46 passing (130ms)
  1 failing

  1) TodoMVC actions Should create an action to delete all completed todos:
     TypeError: _todomvc2.default.actions.deleteCompletedTodos is not a function
      at Context.<anonymous> (tests/todomvc/actions.spec.js:55:28)

Write the code to make the test pass.

diff --git a/src/todomvc/ActionTypes.js b/src/todomvc/ActionTypes.js
index 151042a..6e73656 100644
--- a/src/todomvc/ActionTypes.js
+++ b/src/todomvc/ActionTypes.js
@@ -5,3 +5,4 @@ export const EDIT = 'todomvc/EDIT';
 export const DELETE = 'todomvc/DELETE';
 export const TOGGLE_COMPLETE_ONE = 'todomvc/TOGGLE_COMPLETE_ONE';
 export const TOGGLE_COMPLETE_ALL = 'todomvc/TOGGLE_COMPLETE_ALL';
+export const DELETE_COMPLETED = 'todomvc/DELETE_COMPLETED';
diff --git a/src/todomvc/actions.js b/src/todomvc/actions.js
index f9d165e..92757bc 100644
--- a/src/todomvc/actions.js
+++ b/src/todomvc/actions.js
@@ -21,3 +21,7 @@ export function toggleCompleteOneTodo(id) {
 export function toggleCompleteAllTodos(all_completed = true) {
   return {type: types.TOGGLE_COMPLETE_ALL, all_completed: all_completed}
 }
+
+export function deleteCompletedTodos() {
+  return {type: types.DELETE_COMPLETED}
+}
(END)

Run the test and watch it pass.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo


  47 passing (117ms)

Commit changes.

$ git add .
$ git commit -m 'created an action to delete all completed todos'

TodoMVC reducer

It should handle DELETE_COMPLETED todo

Write the test.

diff --git a/tests/todomvc/reducer.spec.js b/tests/todomvc/reducer.spec.js
index 9a0defd..660a7c5 100644
--- a/tests/todomvc/reducer.spec.js
+++ b/tests/todomvc/reducer.spec.js
@@ -89,5 +89,20 @@ describe('TodoMVC reducer', () => {

     expect(new_state_2.size).to.equal(5);
     new_state_2.map(todo => expect(todo.get('completed')).to.equal(false))
+  });
+
+  it('Should handle DELETE_COMPLETED todo', () => {
+    const state = List([
+      Map({id: uuid.v4(), description: 'My todo', completed: false}),
+      Map({id: uuid.v4(), description: 'My soon to be deleted todo 1', completed: true}),
+      Map({id: uuid.v4(), description: 'My soon to be deleted todo 2', completed: true}),
+      Map({id: uuid.v4(), description: 'My soon to be deleted todo 3', completed: true})
+    ]);
+    const new_state = todomvc.reducer(state, {
+      type: todomvc.types.DELETE_COMPLETED
+    });
+
+    expect(new_state.size).to.equal(1);
+    expect(new_state.get(0)).to.equal(state.get(0))
   })
 });
(END)

Run the test and watch it fail.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    1) Should handle DELETE_COMPLETED todo


  47 passing (160ms)
  1 failing

  1) TodoMVC reducer Should handle DELETE_COMPLETED todo:

      AssertionError: expected 4 to equal 1
      + expected - actual

      -4
      +1

      at Context.<anonymous> (tests/todomvc/reducer.spec.js:105:31)

Write the code to make the test pass.

diff --git a/src/todomvc/reducer.js b/src/todomvc/reducer.js
index 0321650..e49dab1 100644
--- a/src/todomvc/reducer.js
+++ b/src/todomvc/reducer.js
@@ -17,6 +17,8 @@ export default function reducer(state = List([]), action) {
       return (state.map(todo => todo.get('id') === action.id ? todo.set('completed', !todo.get('completed')) : todo));
     case types.TOGGLE_COMPLETE_ALL:
       return state.map(todo => todo.set('completed', action.all_completed));
+    case types.DELETE_COMPLETED:
+      return state.filter(todo => todo.get('completed') === false);
     default:
       // just return the same state
       return (state)
(END)

Run the test and watch it pass.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  48 passing (122ms)

Commit changes.

$ git add .
$ git commit -m 'handle DELETE_COMPLETED todo'

It should render correctly

It should have a 'delete completed' button

Write the test.

diff --git a/tests/todomvc/components/Footer.spec.js b/tests/todomvc/components/Footer.spec.js
index 5ab9f94..58ad563 100644
--- a/tests/todomvc/components/Footer.spec.js
+++ b/tests/todomvc/components/Footer.spec.js
@@ -68,6 +68,14 @@ describe('Footer component', () => {

       expect(label.type()).to.equal('label');
       expect(label.text()).to.equal('The number of todos not completed: 5 todos left')
+    });
+
+    it('Should have a delete all completed button', () => {
+      const {component} = setup();
+      const button = component.children('button');
+
+      expect(button).to.have.length(1);
+      expect(button.children().text()).to.equal('delete completed')
     })
   })
 });
(END)

Run the test and watch it fail.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
      1) Should have a delete all completed button

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  48 passing (157ms)
  1 failing

  1) Footer component Should render correctly Should have a delete all completed button:

      AssertionError: expected { Object (root, unrendered, ...) } to have a length of 1 but got 0
      + expected - actual

      -0
      +1

      at Context.<anonymous> (tests/todomvc/components/Footer.spec.js:77:30)

Write the code to make the test pass.

diff --git a/src/todomvc/components/Footer.js b/src/todomvc/components/Footer.js
index 305053d..7adfdca 100644
--- a/src/todomvc/components/Footer.js
+++ b/src/todomvc/components/Footer.js
@@ -21,6 +21,7 @@ export default class Footer extends Component {
     return (
       <footer>
         <label>The number of todos not completed: { Footer.countNotCompleted(todos) }</label>
+        <button>delete completed</button>
       </footer>
     )
   }
(END)

Run the test and watch it pass.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
      ✓ Should have a delete all completed button

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  49 passing (169ms)

Commit changes.

$ git add .
$ git commit -m 'have a delete completed button'

It should behave correctly

It should not show 'delete completed' button when there are no completed todos

We have just created a test to confirm the existence of the 'delete completed' button. Let's replace that unconditional statement with two tests that will confirm that the button will exist if and only if there is at least one completed todo.

Let's start with the case when there are no completed todos.

Write the test.

diff --git a/tests/todomvc/components/Footer.spec.js b/tests/todomvc/components/Footer.spec.js
index 58ad563..9e60c17 100644
--- a/tests/todomvc/components/Footer.spec.js
+++ b/tests/todomvc/components/Footer.spec.js
@@ -68,14 +68,20 @@ describe('Footer component', () => {

       expect(label.type()).to.equal('label');
       expect(label.text()).to.equal('The number of todos not completed: 5 todos left')
-    });
+    })
+  });

-    it('Should have a delete all completed button', () => {
-      const {component} = setup();
+  describe('Should behave correctly', () => {
+    it('Should not show \'delete completed\' button when there are no completed todos', () => {
+      const todos = List([
+        Map({id: uuid.v4(), description: 'todo 1', completed: false}),
+        Map({id: uuid.v4(), description: 'todo 2', completed: false}),
+        Map({id: uuid.v4(), description: 'todo 3', completed: false})
+      ]);
+      const {component} = setup(todos);
       const button = component.children('button');

-      expect(button).to.have.length(1);
-      expect(button.children().text()).to.equal('delete completed')
+      expect(button).to.have.length(0)
     })
   })
 });
(END)

Run the test and watch it fail.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
    Should behave correctly
      1) Should not show 'delete completed' button when there are no completed todos

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  48 passing (170ms)
  1 failing

  1) Footer component Should behave correctly Should not show 'delete completed' button when there are no completed todos:

      AssertionError: expected { Object (root, unrendered, ...) } to have a length of 0 but got 1
      + expected - actual

      -1
      +0

      at Context.<anonymous> (tests/todomvc/components/Footer.spec.js:84:30)

Write the code to make the test pass.

diff --git a/src/todomvc/components/Footer.js b/src/todomvc/components/Footer.js
index ab31634..58a2745 100644
--- a/src/todomvc/components/Footer.js
+++ b/src/todomvc/components/Footer.js
@@ -8,6 +8,11 @@ export default class Footer extends Component {
     todos: PropTypes.object
   };

+  static hasCompleted(todos) {
+    if (typeof todos === "undefined") return false;
+    else return todos.filter(todo => todo.get('completed') === true).count() > 0;
+  }
+
   static countNotCompleted(todos) {
     if (typeof todos === "undefined") return '';
     else if (todos.filter(todo => todo.get('completed') !== true).count() === 0) return 'No todos left';
@@ -21,7 +26,7 @@ export default class Footer extends Component {
     return (
       <footer>
         <label>The number of todos not completed: {Footer.countNotCompleted(todos)}</label>
-        <button>delete completed</button>
+        { Footer.hasCompleted(todos) && <button>delete completed</button> }
       </footer>
     )
   }
(END)

Run the test and watch it pass.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
    Should behave correctly
      ✓ Should not show 'delete completed' button when there are no completed todos

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  49 passing (129ms)

The change we just introduced in /src/todomvc/components/Footer.js should satisfy our second test case as well. Let's confirm that:

Write the test.

diff --git a/tests/todomvc/components/Footer.spec.js b/tests/todomvc/components/Footer.spec.js
index 9768562..6626897 100644
--- a/tests/todomvc/components/Footer.spec.js
+++ b/tests/todomvc/components/Footer.spec.js
@@ -82,6 +82,19 @@ describe('Footer component', () => {
       const button = component.children('button');

       expect(button).to.have.length(0)
+    });
+
+    it('Should show \'delete completed\' button when there is at least one completed todo', () => {
+      const todos = List([
+        Map({id: uuid.v4(), description: 'todo 1', completed: false}),
+        Map({id: uuid.v4(), description: 'todo 2', completed: true}),
+        Map({id: uuid.v4(), description: 'todo 3', completed: false})
+      ]);
+      const {component} = setup(todos);
+      const button = component.children('button');
+
+      expect(button).to.have.length(1);
+      expect(button.children().text()).to.equal('delete completed')
     })
   })
 });
(END)

And run the test and watch it pass.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
    Should behave correctly
      ✓ Should not show 'delete completed' button when there are no completed todos
      ✓ Should show 'delete completed' button when there is at least one completed todo

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  50 passing (174ms)

Commit changes.

$ git add .
$ git commit -m 'delete completed button renders as expected'

Should call deleteCompletedTodos() when the delete completed button is clicked

Write the test

diff --git a/tests/todomvc/components/Footer.spec.js b/tests/todomvc/components/Footer.spec.js
index 6626897..17fd494 100644
--- a/tests/todomvc/components/Footer.spec.js
+++ b/tests/todomvc/components/Footer.spec.js
@@ -5,12 +5,14 @@ import {List, Map} from 'immutable'
 import uuid from 'uuid'
 import {expect} from 'chai'
 import {shallow} from 'enzyme'
+import sinon from 'sinon'

 import Footer from '../../../src/todomvc/components/Footer'

 function setup(todos) {
   const props = {
-    todos: todos
+    todos: todos,
+    deleteCompletedTodos: sinon.spy()
   };

   const component = shallow(
@@ -18,7 +20,8 @@ function setup(todos) {
   );

   return {
-    component: component
+    component: component,
+    props: props
   }
 }

@@ -95,6 +98,19 @@ describe('Footer component', () => {

       expect(button).to.have.length(1);
       expect(button.children().text()).to.equal('delete completed')
+    });
+
+    it('Should call deleteCompletedTodos() when the delete completed button is clicked', () => {
+      const todos = List([
+        Map({id: uuid.v4(), description: 'todo 1', completed: false}),
+        Map({id: uuid.v4(), description: 'todo 2', completed: true}),
+        Map({id: uuid.v4(), description: 'todo 3', completed: false})
+      ]);
+      const {component, props} = setup(todos);
+      const button = component.find('button');
+
+      button.simulate('click');
+      expect(props.deleteCompletedTodos.called).to.be.true
     })
   })
 });
(END)

Run the test and watch it fail

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
    Should behave correctly
      ✓ Should not show 'delete completed' button when there are no completed todos
      ✓ Should show 'delete completed' button when there is at least one completed todo
      1) Should call deleteCompletedTodos() when the delete completed button is clicked

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  50 passing (136ms)
  1 failing

  1) Footer component Should behave correctly Should call deleteCompletedTodos() when the delete completed button is clicked:

      AssertionError: expected false to be true
      + expected - actual

      -false
      +true

      at Context.<anonymous> (tests/todomvc/components/Footer.spec.js:113:7)

Write the code to make the test pass.

diff --git a/src/todomvc/components/Footer.js b/src/todomvc/components/Footer.js
index 58a2745..cfed381 100644
--- a/src/todomvc/components/Footer.js
+++ b/src/todomvc/components/Footer.js
@@ -5,7 +5,8 @@ import PropTypes from 'prop-types'

 export default class Footer extends Component {
   static propTypes = {
-    todos: PropTypes.object
+    todos: PropTypes.object,
+    deleteCompletedTodos: PropTypes.func.isRequired
   };

   static hasCompleted(todos) {
@@ -21,12 +22,13 @@ export default class Footer extends Component {
   }

   render() {
-    const {todos} = this.props;
+    const todos = this.props.todos;
+    const deleteCompletedTodos = this.props.deleteCompletedTodos;

     return (
       <footer>
         <label>The number of todos not completed: {Footer.countNotCompleted(todos)}</label>
-        { Footer.hasCompleted(todos) && <button>delete completed</button> }
+        {Footer.hasCompleted(todos) && <button onClick={() => deleteCompletedTodos()}>delete completed</button>}
       </footer>
     )
   }
diff --git a/src/todomvc/components/MainSection.js b/src/todomvc/components/MainSection.js
index 95a3e39..9ad15c8 100644
--- a/src/todomvc/components/MainSection.js
+++ b/src/todomvc/components/MainSection.js
@@ -65,7 +65,7 @@ export default class MainSection extends Component {
               name="complete_status"
               checked={this.state.show_not_completed}
               onChange={this.setVisibility.bind(this)}/> show not completed
-        <Footer todos={todos}/>
+        <Footer todos={todos} deleteCompletedTodos={actions.deleteCompletedTodos}/>
      </section>
    )
  }
(END)

Run the test and watch it pass.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
    Should behave correctly
      ✓ Should not show 'delete completed' button when there are no completed todos
      ✓ Should show 'delete completed' button when there is at least one completed todo
      ✓ Should call deleteCompletedTodos() when the delete completed button is clicked

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
Warning: Failed prop type: The prop `deleteCompletedTodos` is marked as required in `Footer`, but its value is `undefined`.
    in Footer
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  51 passing (140ms)

Let's fix the warning the same way we did before. Write the code to make the warning go away.

diff --git a/tests/todomvc/components/MainSection.spec.js b/tests/todomvc/components/MainSection.spec.js
index 829cbf5..cc9dcc2 100644
--- a/tests/todomvc/components/MainSection.spec.js
+++ b/tests/todomvc/components/MainSection.spec.js
@@ -22,7 +22,8 @@ function setup() {
     actions: {
       editTodo: sinon.spy(),
       deleteTodo: sinon.spy(),
-      toggleCompleteOneTodo: sinon.spy()
+      toggleCompleteOneTodo: sinon.spy(),
+      deleteCompletedTodos: sinon.spy()
     }
   };

(END)

Run the test and watch it pass without the warning.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo
    ✓ Should create an action to delete a todo
    ✓ Should create an action to toggle a todo between completed and not completed
    ✓ Should create an action to toggle all todos between completed and not completed
    ✓ Should create an action to delete all completed todos

  Footer component
    Should render correctly
      ✓ Should be a Footer
      ✓ Should have a todo counter
      ✓ Should display 'No todos left' when active count is 0
      ✓ Should display '1 todo left' when active count is 1
      ✓ Should display '5 todos left' when active count is 5
    Should behave correctly
      ✓ Should not show 'delete completed' button when there are no completed todos
      ✓ Should show 'delete completed' button when there is at least one completed todo
      ✓ Should call deleteCompletedTodos() when the delete completed button is clicked

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
      ✓ Should have a toggle all complete status checkbox
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0
      ✓ Should call toggleCompleteAllTodos() when the all complete status checkbox is changed

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos
      ✓ Should include a Footer component
      ✓ Should include a completed radio-button filter
    Should behave correctly
      ✓ Should show the filtered list of Todos

  TodoApp component
    Should render correctly
      ✓ Should be a TodoApp
      ✓ Should have a header
      ✓ Should have a main section

  TodoItem component
    Should render correctly
      ✓ Should be an li
      ✓ Should have a label
      ✓ Should have a delete button
      ✓ Should have a toggle complete status checkbox
    Should behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      ✓ Should leave edit mode after TodoTextInput onSave
      ✓ Should call deleteTodo() when the delete button is clicked
      ✓ Should call deleteTodo() when TodoTextInput onSave is called with no text
      ✓ Should call toggleCompleteOneTodo() when the complete status checkbox is changed

  TodoTextInput component
    ✓ Should not call onSave() on blur if isNew
    Should render correctly
      ✓ Should be a TodoTextInput component
    Should behave correctly
      ✓ Should update value on change
      ✓ Should call onSave() on return key press
      ✓ Should reset state on return key press if isNew
      ✓ Should call onSave() on blur if not isNew

  TodoMVC reducer
    ✓ Should handle initial state
    ✓ Should handle ADD todo
    ✓ Should handle EDIT todo
    ✓ Should handle DELETE todo
    ✓ Should handle TOGGLE_COMPLETE_ONE todo
    ✓ Should handle TOGGLE_COMPLETE_ALL todo
    ✓ Should handle DELETE_COMPLETED todo


  51 passing (143ms)

Commit changes.

$ git add .
$ git commit -m 'call deleteCompletedTodos() when the delete completed button is clicked'

Run It!

Now let's see what this looks like.

Open http://localhost:4000 in your web browser.

Create a couple of todos.

Change the status to 'completed' to some of the created todos. Observe 'delete completed' button appearing.

Click the button. The completed todos will be deleted and the button itself will disappear.

Last updated

Was this helpful?