Filter the Todos List Between Completed, Not Completed, and All

Story: As a user, I want to be able to filter All, Active, and Completed todos.

MainSection component

Should render correctly

It should include a completed radio-button filter

Write the test.

diff --git a/tests/todomvc/components/MainSection.spec.js b/tests/todomvc/components/MainSection.spec.js
index 9ffc1de..259b510 100644
--- a/tests/todomvc/components/MainSection.spec.js
+++ b/tests/todomvc/components/MainSection.spec.js
@@ -59,6 +59,18 @@ describe('MainSection component', () => {
       const footer = component.children('Footer');

       expect(footer).to.have.length(1);
+    });
+
+    it('Should include a completed radio-button filter', () => {
+      const {component} = setup();
+      const radio_buttons = component.children('input');
+
+      expect(radio_buttons).to.have.length(3);
+      radio_buttons.map(radio_button => expect(radio_button.props().type).to.equal('radio'));
+      radio_buttons.map(radio_button => expect(radio_button.props().name).to.equal('complete_status'));
+      expect(radio_buttons.nodes[0].props.value).to.equal('show_all');
+      expect(radio_buttons.nodes[1].props.value).to.equal('show_completed');
+      expect(radio_buttons.nodes[2].props.value).to.equal('show_not_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

  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
      1) Should include a completed radio-button filter

  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


  44 passing (110ms)
  1 failing

  1) MainSection component Should render correctly Should include a completed radio-button filter:

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

      -0
      +3

      at Context.<anonymous> (tests/todomvc/components/MainSection.spec.js:68:37)

Write the code to make the test pass.

diff --git a/src/todomvc/components/MainSection.js b/src/todomvc/components/MainSection.js
index 5a40455..c9cdcc0 100644
--- a/src/todomvc/components/MainSection.js
+++ b/src/todomvc/components/MainSection.js
@@ -12,6 +12,15 @@ export default class MainSection extends Component {
     actions: PropTypes.object.isRequired
   };

+  constructor(props, context) {
+    super(props, context);
+    this.state = {
+      show_all: true,
+      show_completed: false,
+      show_not_completed: false
+    }
+  }
+
   render() {
     const {todos, actions} = this.props;

@@ -21,5 +30,17 @@ export default class MainSection extends Component {
           {todos.map(todo => <TodoItem key={todo.get('id')} todo={todo} {...actions} />)}
         </ul>
+        <input type="radio"
+               value="show_all"
+               name="complete_status"
+               checked={this.state.show_all}/> show all
+        <input type="radio"
+               value="show_completed"
+               name="complete_status"
+               checked={this.state.show_completed}/> show completed
+        <input type="radio"
+               value="show_not_completed"
+               name="complete_status"
+               checked={this.state.show_not_completed}/> show not completed
         <Footer todos={todos}/>
       </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

  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

  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


  45 passing (107ms)

Commit changes.

$ git add .
$ git commit -m 'completed radio-button filter'

Should behave correctly

It should show the filtered list of Todos

We will set a state of five todos, two 'completed' and three 'not completed'. Testing will be performed in four phases:

  • Initial render should display all five todos with both completed and not completed status.

  • After the simulation of change of 'show only completed' radio button, render should display only two completed todos.

  • After the simulation of change of 'show only not completed' radio button, render should display only three not completed todos.

  • After the simulation of change of 'show all' radio button, render should display all five todos.

Write the test.

diff --git a/tests/todomvc/components/MainSection.spec.js b/tests/todomvc/components/MainSection.spec.js
index 259b510..f9a4f4e 100644
--- a/tests/todomvc/components/MainSection.spec.js
+++ b/tests/todomvc/components/MainSection.spec.js
@@ -14,6 +14,9 @@ function setup() {
   const props = {
     todos: List([
       Map({id: uuid.v4(), description: 'Use Redux', completed: false}),
+      Map({id: uuid.v4(), description: 'Use Redux 2', completed: true}),
+      Map({id: uuid.v4(), description: 'Use Redux 3', completed: true}),
+      Map({id: uuid.v4(), description: 'Use Redux 4', completed: false}),
       Map({id: uuid.v4(), description: 'Run the tests', completed: false})
     ]),
     actions: {
@@ -72,5 +75,49 @@ describe('MainSection component', () => {
       expect(radio_buttons.nodes[1].props.value).to.equal('show_completed');
       expect(radio_buttons.nodes[2].props.value).to.equal('show_not_completed')
     })
+  });
+
+  describe('Should behave correctly', () => {
+    it('Should show the filtered list of Todos', () => {
+      const {component} = setup();
+      let todos = component.children('ul').children().nodes;
+      const radio_button_all = component.find('#id_show_all');
+      const radio_button_completed = component.find('#id_show_completed');
+      const radio_button_not_completed = component.find('#id_show_not_completed');
+
+      let and_result = true;
+      let or_result = false;
+      expect(todos).to.have.length(5);
+      todos.map(todo => {
+        expect([true, false]).to.include(todo.props.todo.get('completed'));
+        and_result = and_result && todo.props.todo.get('completed');
+        or_result = or_result || todo.props.todo.get('completed')
+      });
+      expect(and_result).to.equal(false);
+      expect(or_result).to.equal(true);
+
+      radio_button_completed.simulate('change', {target: {value: 'show_completed'}});
+      todos = component.children('ul').children().nodes;
+      expect(todos).to.have.length(2);
+      todos.map(todo => expect([true]).to.include(todo.props.todo.get('completed')));
+
+      radio_button_not_completed.simulate('change', {target: {value: 'show_not_completed'}});
+      todos = component.children('ul').children().nodes;
+      expect(todos).to.have.length(3);
+      todos.map(todo => expect([false]).to.include(todo.props.todo.get('completed')));
+
+      and_result = true;
+      or_result = false;
+      radio_button_all.simulate('change', {target: {value: 'show_all'}});
+      todos = component.children('ul').children().nodes;
+      expect(todos).to.have.length(5);
+      todos.map(todo => {
+        expect([true, false]).to.include(todo.props.todo.get('completed'));
+        and_result = and_result && todo.props.todo.get('completed');
+        or_result = or_result || todo.props.todo.get('completed')
+      });
+      expect(and_result).to.equal(false);
+      expect(or_result).to.equal(true)
+    })
   })
 });
(END)

Notes:

  • with .simulate() method from Enzyme package we are providing a second (optional) parameter, a mock event object that will be merged with the event object passed to the handlers. This is the value of the radio button we are trying to produce. We do this because .simulate() doesn't create the event object in our case.

  • In the first and the fourth phase of the testing we want to assert that todos list contains both completed and not completed todos. We are basing our assertion on the facts that:

    • OR(T1, T2, ..., Tn) == true if-and-only-if at least one of (T1, ..., Tn) is true

    • AND(T1, T2, ..., Tn) == false if-and-only-if at least one of (T1, ..., Tn) is false

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

  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
      1) 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


  45 passing (112ms)
  1 failing

  1) MainSection component Should behave correctly Should show the filtered list of Todos:
     Error: Method “props” is only meant to be run on a single node. 0 found instead.
      at ShallowWrapper.single (node_modules/enzyme/build/ShallowWrapper.js:1516:17)
      at ShallowWrapper.props (node_modules/enzyme/build/ShallowWrapper.js:867:21)
      at ShallowWrapper.prop (node_modules/enzyme/build/ShallowWrapper.js:1075:21)
      at ShallowWrapper.simulate (node_modules/enzyme/build/ShallowWrapper.js:838:28)
      at Context.<anonymous> (tests/todomvc/components/MainSection.spec.js:99:30)

Write the code to make the test pass.

diff --git a/src/todomvc/components/MainSection.js b/src/todomvc/components/MainSection.js
index c9cdcc0..95a3e39 100644
--- a/src/todomvc/components/MainSection.js
+++ b/src/todomvc/components/MainSection.js
@@ -21,27 +21,47 @@ export default class MainSection extends Component {
     }
   }

+  setVisibility(event) {
+    this.setState({
+      show_all: event.target.value === 'show_all',
+      show_completed: event.target.value === 'show_completed',
+      show_not_completed: event.target.value === 'show_not_completed'
+    });
+  }
+
+  showTodo(status) {
+    if (this.state.show_completed && status) return true;
+    else if (this.state.show_not_completed && !status) return true;
+    else return this.state.show_all;
+  }
+
   render() {
     const {todos, actions} = this.props;

     return (
       <section>
         <ul>
-          {todos.map(todo => <TodoItem key={todo.get('id')} todo={todo} {...actions} />)}
+          {todos.map(todo => this.showTodo.bind(this)(todo.get('completed')) &&
+            <TodoItem key={todo.get('id')} todo={todo} {...actions} />)}
         </ul>
-        <input type="radio"
+        <input id="id_show_all"
+               type="radio"
                value="show_all"
                name="complete_status"
-               checked={this.state.show_all}/> show all
-        <input type="radio"
+               checked={this.state.show_all}
+               onChange={this.setVisibility.bind(this)}/> show all
+        <input id="id_show_completed"
+               type="radio"
                value="show_completed"
                name="complete_status"
-               checked={this.state.show_completed}/> show completed
-        <input type="radio"
+               checked={this.state.show_completed}
+               onChange={this.setVisibility.bind(this)}/> show completed
+        <input id="id_show_not_completed"
+               type="radio"
                value="show_not_completed"
                name="complete_status"
-               checked={this.state.show_not_completed}/> show not completed
+               checked={this.state.show_not_completed}
+               onChange={this.setVisibility.bind(this)}/> show not completed
         <Footer todos={todos}/>
       </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

  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 (114ms)

Commit changes.

$ git add .
$ git commit -m "show the filtered list of todos"

Run It!

Now let's see what this looks like.

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

You should immediately see the three-radiobutton set being set to show all todos.

Create a couple of todos and set some of them to be completed

Set the visibility filter to completed only. Check what you see in the todos list.

Set the visibility filter to not completed only. Check what you see in the todos list.

Last updated

Was this helpful?