Edit a Todo

Story: As a user, I want to edit a todo in my todos list

TodoMVC actions

It should create an action to edit a todo

Write the test. Notice that we're using the string literal 'todomvc/EDIT' for the action type, rather todomvc.types.EDIT. The latter allows the test to pass when todomvc.types.EDIT is undefined, since both the created action and the expected action types will be undefined and therefore equal.

diff --git a/tests/todomvc/actions.spec.js b/tests/todomvc/actions.spec.js
index 614f483..694dd04 100644
--- a/tests/todomvc/actions.spec.js
+++ b/tests/todomvc/actions.spec.js
@@ -12,5 +12,14 @@ describe('TodoMVC actions', () => {
     };

     expect(todomvc.actions.addTodo(description)).to.deep.equal(expectedAction)
+  });
+
+  it('Should create an action to edit a todo', () => {
+    const description = 'My todo';
+    const expectedAction = {
+      type: 'todomvc/EDIT', id: 'my_id', description: description
+    };
+
+    expect(todomvc.actions.editTodo('my_id', description)).to.deep.equal(expectedAction)
   })
 });
(END)

Run the test and watch it fail.

  TodoMVC actions
    ✓ Should create an action to add a todo
    1) Should create an action to edit a todo

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos

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

  TodoItem component
    ✓ Should have a label
    Should render correctly
      ✓ Should be an li

  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


  20 passing (76ms)
  1 failing

  1) TodoMVC actions Should create an action to edit a todo:
     TypeError: _todomvc2.default.actions.editTodo is not a function
      at Context.<anonymous> (tests/todomvc/actions.spec.js:23:28)

Write the code to make the test pass.

diff --git a/src/todomvc/actions.js b/src/todomvc/actions.js
index bfa600f..dc30850 100644
--- a/src/todomvc/actions.js
+++ b/src/todomvc/actions.js
@@ -5,3 +5,7 @@ import * as types from './ActionTypes'
 export function addTodo(text) {
   return {type: types.ADD, description: text, completed: false}
 }
+
+export function editTodo(id, text) {
+  return {type: types.EDIT, id: id, description: text}
+}
(END)

Run the test and notice that it still fails. This is because we haven't defined the EDIT action type, yet.

  TodoMVC actions
    ✓ Should create an action to add a todo
    1) Should create an action to edit a todo

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos

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

  TodoItem component
    ✓ Should have a label
    Should render correctly
      ✓ Should be an li

  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


  20 passing (80ms)
  1 failing

  1) TodoMVC actions Should create an action to edit a todo:

      AssertionError: expected { Object (type, id, ...) } to deeply equal { Object (type, id, ...) }
      + expected - actual

       {
         "description": "My todo"
         "id": "my_id"
      -  "type": [undefined]
      +  "type": "todomvc/EDIT"
       }

      at Context.<anonymous> (tests/todomvc/actions.spec.js:23:68)

Define the action type.

diff --git a/src/todomvc/ActionTypes.js b/src/todomvc/ActionTypes.js
index a135148..28b3b32 100644
--- a/src/todomvc/ActionTypes.js
+++ b/src/todomvc/ActionTypes.js
@@ -1,3 +1,4 @@
 'use strict';

 export const ADD = 'todomvc/ADD';
+export const EDIT = 'todomvc/EDIT';
(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

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos

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

  TodoItem component
    ✓ Should have a label
    Should render correctly
      ✓ Should be an li

  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


  21 passing (77ms)

Commit changes.

$ git add .
$ git commit -m 'created an action to edit a todo'

TodoMVC reducer

It should handle EDIT todo

Write the test.

diff --git a/tests/todomvc/reducer.spec.js b/tests/todomvc/reducer.spec.js
index 9788529..0c4fb5e 100644
--- a/tests/todomvc/reducer.spec.js
+++ b/tests/todomvc/reducer.spec.js
@@ -1,7 +1,8 @@
 'use strict';

 import {expect} from 'chai'
-import {fromJS, List} from 'immutable'
+import {fromJS, List, Map} from 'immutable'
+import uuid from 'uuid'

 import todomvc from '../../src/todomvc'

@@ -22,5 +23,23 @@ describe('TodoMVC reducer', () => {
     expect(new_state.first().filterNot((v, k) => k === 'id')).to.equal(fromJS({ // skip uuid id field
       description: description, completed: false
     }))
+  });
+
+  it('Should handle EDIT todo', () => {
+    const state = List([
+      Map({id: uuid.v4(), description: 'My todo', completed: false}),
+      Map({id: uuid.v4(), description: 'My next todo', completed: false})
+    ]);
+    const new_state = todomvc.reducer(state, {
+      type: todomvc.types.EDIT,
+      id: state.get(1).get('id'),
+      description: 'My updated todo'
+    });
+
+    expect(new_state.size).to.equal(2);
+    expect(new_state.get(0)).to.equal(state.get(0));
+    expect(new_state.get(1).get('id')).to.equal(state.get(1).get('id'));
+    expect(new_state.get(1).get('description')).to.equal('My updated todo');
+    expect(new_state.get(1).get('completed')).to.equal(state.get(1).get('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

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos

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

  TodoItem component
    ✓ Should have a label
    Should render correctly
      ✓ Should be an li

  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
    1) Should handle EDIT todo


  21 passing (76ms)
  1 failing

  1) TodoMVC reducer Should handle EDIT todo:

      AssertionError: expected 'My next todo' to equal 'My updated todo'
      + expected - actual

      -My next todo
      +My updated todo

      at Context.<anonymous> (tests/todomvc/reducer.spec.js:42:52)

Write the code to make the test pass.

diff --git a/src/todomvc/reducer.js b/src/todomvc/reducer.js
index ea9495e..b79948a 100644
--- a/src/todomvc/reducer.js
+++ b/src/todomvc/reducer.js
@@ -9,6 +9,8 @@ export default function reducer(state = List([]), action) {
   switch (action.type) {
     case types.ADD:
       return (state.push(Map({id: uuid.v4(), description: action.description, completed: false})));
+    case types.EDIT:
+      return (state.map(todo => todo.get('id') === action.id ? todo.set('description', action.description) : todo));
     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

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a list of todos

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

  TodoItem component
    ✓ Should have a label
    Should render correctly
      ✓ Should be an li

  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


  22 passing (74ms)

Commit changes.

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

TodoItem component

Should behave correctly

It should switch to edit mode when label onDoubleClick is fired

Write the test.

diff --git a/tests/todomvc/components/TodoItem.spec.js b/tests/todomvc/components/TodoItem.spec.js
index 772bb61..e37e04c 100644
--- a/tests/todomvc/components/TodoItem.spec.js
+++ b/tests/todomvc/components/TodoItem.spec.js
@@ -37,5 +37,18 @@ describe('TodoItem component', () => {
       expect(label).to.have.length(1);
       expect(label.children().text()).to.equal(props.todo.get('description'))
     })
+  });
+
+  describe('Should behave correctly', () => {
+    it('Should switch to edit mode when label onDoubleClick is fired', () => {
+      const {component} = setup();
+
+      component.children('label').simulate('doubleclick'); // switch to edit mode
+
+      const input = component.find('TodoTextInput');
+
+      expect(input).to.have.length(1);
+      expect(input.prop('text')).to.equal('Use Redux')
+    })
   })
 });
(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

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a 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 behave correctly
      1) Should switch to edit mode when label onDoubleClick is fired

  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


  22 passing (81ms)
  1 failing

  1) TodoItem component Should behave correctly Should switch to edit mode when label onDoubleClick is fired:

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

      -0
      +1

      at Context.<anonymous> (tests/todomvc/components/TodoItem.spec.js:50:29)

Write the code to make the test pass.

diff --git a/src/todomvc/components/TodoItem.js b/src/todomvc/components/TodoItem.js
index 6afa1eb..522b83a 100644
--- a/src/todomvc/components/TodoItem.js
+++ b/src/todomvc/components/TodoItem.js
@@ -2,21 +2,47 @@

 import React, {Component} from 'react'
 import PropTypes from 'prop-types'
+import TodoTextInput from './TodoTextInput'

 export default class TodoItem extends Component {
   static propTypes = {
     todo: PropTypes.object.isRequired
   };

-  render() {
+  constructor(props, context) {
+    super(props, context);
+    this.state = {
+      editing: false
+    }
+  }
+
+  handleDoubleClick() {
+    this.setState({editing: true})
+  }
+
+  renderTodoListItem() {
     const {todo} = this.props;

     return (
       <li>
-        <label>
+        <label onDoubleClick={ this.handleDoubleClick.bind(this) }>
           { todo.get('description') }
         </label>
       </li>
     )
   }
+
+  renderTodoTextInput() {
+    const {todo} = this.props;
+
+    return (
+      <li>
+        <TodoTextInput text={ todo.get('description') }/>
+      </li>
+    )
+  }
+
+  render() {
+    return ( this.state.editing ? this.renderTodoTextInput() : this.renderTodoListItem() )
+  }
 }
(END)

Run the test and watch it pass. We'll get a warning from TodoTextInput because we haven't implement the onSave handler yet.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a 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 behave correctly
Warning: Failed prop type: The prop `onSave` is marked as required in `TodoTextInput`, but its value is `undefined`.
    in TodoTextInput
      ✓ Should switch to edit mode when label onDoubleClick is fired

  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


  23 passing (82ms)

Commit changes.

$ git add .
$ git commit -m 'switch to edit mode when label onDoubleClick is fired'

It should call editTodo() when TodoTextInput onSave is called

Write the test.

diff --git a/tests/todomvc/components/TodoItem.spec.js b/tests/todomvc/components/TodoItem.spec.js
index e37e04c..29e77ec 100644
--- a/tests/todomvc/components/TodoItem.spec.js
+++ b/tests/todomvc/components/TodoItem.spec.js
@@ -5,12 +5,14 @@ import {Map} from 'immutable'
 import uuid from 'uuid'
 import {expect} from 'chai'
 import {shallow} from 'enzyme'
+import sinon from 'sinon'

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

 function setup() {
   const props = {
-    todo: Map({id: uuid.v4(), description: 'Use Redux', completed: false})
+    todo: Map({id: uuid.v4(), description: 'Use Redux', completed: false}),
+    editTodo: sinon.spy()
   };
   const component = shallow(
     <TodoItem {...props} />
@@ -49,6 +51,14 @@ describe('TodoItem component', () => {

       expect(input).to.have.length(1);
       expect(input.prop('text')).to.equal('Use Redux')
+    });
+
+    it('Should call editTodo() when TodoTextInput onSave is called', () => {
+      const {component, props} = setup();
+
+      component.children('label').simulate('doubleclick'); // switch to edit mode
+      component.find('TodoTextInput').props().onSave('Use Redux');
+      expect(props.editTodo.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

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a 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 behave correctly
Warning: Failed prop type: The prop `onSave` is marked as required in `TodoTextInput`, but its value is `undefined`.
    in TodoTextInput
      ✓ Should switch to edit mode when label onDoubleClick is fired
      1) Should call editTodo() when TodoTextInput onSave is called

  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


  23 passing (80ms)
  1 failing

  1) TodoItem component Should behave correctly Should call editTodo() when TodoTextInput onSave is called:
     TypeError: component.find(...).props(...).onSave is not a function
      at Context.<anonymous> (tests/todomvc/components/TodoItem.spec.js:60:47)

Write the code to make the test pass.

diff --git a/src/todomvc/components/TodoItem.js b/src/todomvc/components/TodoItem.js
index 522b83a..065df31 100644
--- a/src/todomvc/components/TodoItem.js
+++ b/src/todomvc/components/TodoItem.js
@@ -6,7 +6,8 @@ import TodoTextInput from './TodoTextInput'

 export default class TodoItem extends Component {
   static propTypes = {
-    todo: PropTypes.object.isRequired
+    todo: PropTypes.object.isRequired,
+    editTodo: PropTypes.func.isRequired
   };

   constructor(props, context) {
@@ -20,13 +21,17 @@ export default class TodoItem extends Component {
     this.setState({editing: true})
   }

+  handleSave(id, text) {
+    this.props.editTodo(id, text)
+  }
+
   renderTodoListItem() {
     const {todo} = this.props;

     return (
       <li>
-        <label onDoubleClick={ this.handleDoubleClick.bind(this) }>
-          { todo.get('description') }
+        <label onDoubleClick={this.handleDoubleClick.bind(this)}>
+          {todo.get('description')}
         </label>
       </li>
     )
@@ -37,7 +42,7 @@ export default class TodoItem extends Component {

     return (
       <li>
-        <TodoTextInput text={ todo.get('description') }/>
+        <TodoTextInput text={todo.get('description')} onSave={(text) => this.handleSave(todo.get('id'), text)}/>
       </li>
     )
   }
(END)

Run the test and watch it pass. Notice that the TodoItem tests now all pass, but we caused a regression in the MainSection tests by adding a required prop to TodoItem. Under normal circumstances, we would address this regression immediately in order to prevent it from festering. However, to maintain the flow of the tutorial, we'll first finish working on the TodoItem component.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
Warning: Failed prop type: The prop `editTodo` is marked as required in `TodoItem`, but its value is `undefined`.
    in TodoItem
      ✓ Should be a MainSection component
      ✓ Should include a 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 behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called

  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


  24 passing (87ms)

Commit changes.

$ git add .
$ git commit -m 'should call editTodo() when TodoTextInput onSave is called'

It should leave edit mode after TodoTextInput onSave

Write the test.

diff --git a/tests/todomvc/components/TodoItem.spec.js b/tests/todomvc/components/TodoItem.spec.js
index 29e77ec..50c527e 100644
--- a/tests/todomvc/components/TodoItem.spec.js
+++ b/tests/todomvc/components/TodoItem.spec.js
@@ -59,6 +59,15 @@ describe('TodoItem component', () => {
       component.children('label').simulate('doubleclick'); // switch to edit mode
       component.find('TodoTextInput').props().onSave('Use Redux');
       expect(props.editTodo.called).to.be.true
+    });
+
+    it('Should leave edit mode after TodoTextInput onSave', () => {
+      const {component} = setup();
+
+      component.children('label').simulate('doubleclick'); // switch to edit mode
+      component.find('TodoTextInput').props().onSave('Use Redux'); // update
+      component.update(); // force component to re-render
+      expect(component.children('label')).to.have.length(1) // switched back to label
     })
   })
 });
(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

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
Warning: Failed prop type: The prop `editTodo` is marked as required in `TodoItem`, but its value is `undefined`.
    in TodoItem
      ✓ Should be a MainSection component
      ✓ Should include a 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 behave correctly
      ✓ Should switch to edit mode when label onDoubleClick is fired
      ✓ Should call editTodo() when TodoTextInput onSave is called
      1) Should leave edit mode after TodoTextInput onSave

  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


  24 passing (90ms)
  1 failing

  1) TodoItem component Should behave correctly Should leave edit mode after TodoTextInput onSave:

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

      -0
      +1

      at Context.<anonymous> (tests/todomvc/components/TodoItem.spec.js:70:51)

Write the code to make the test pass.

diff --git a/src/todomvc/components/TodoItem.js b/src/todomvc/components/TodoItem.js
index dc8823b..7dedc01 100644
--- a/src/todomvc/components/TodoItem.js
+++ b/src/todomvc/components/TodoItem.js
@@ -22,7 +22,8 @@ export default class TodoItem extends Component {
   }

   handleSave(id, text) {
-    this.props.editTodo(id, text)
+    this.props.editTodo(id, text);
+    this.setState({editing: false})
   }

   renderTodoListItem() {
(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

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
Warning: Failed prop type: The prop `editTodo` is marked as required in `TodoItem`, but its value is `undefined`.
    in TodoItem
      ✓ Should be a MainSection component
      ✓ Should include a 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 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

  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


  25 passing (89ms)

Commit changes.

$ git add .
$ git commit -m 'leave edit mode after TodoTextInput onSave'

MainSection Component

Add editTodo action to fix required prop warning regression

Write the test. In this case, we're not adding a new test, but fixing a regession in an existing test. We added editTodo as a required prop in the TodoTextInput component, which is a child of the TodoItem component. Now we need to add the editTodo action to our MainSection test setup.

diff --git a/tests/todomvc/components/MainSection.spec.js b/tests/todomvc/components/MainSection.spec.js
index 9e0dedd..6bc0861 100644
--- a/tests/todomvc/components/MainSection.spec.js
+++ b/tests/todomvc/components/MainSection.spec.js
@@ -5,6 +5,7 @@ import {List, Map} from 'immutable'
 import uuid from 'uuid'
 import {expect} from 'chai'
 import {shallow} from 'enzyme'
+import sinon from 'sinon'

 import MainSection from '../../../src/todomvc/components/MainSection'
 import TodoItem from '../../../src/todomvc/components/TodoItem'
@@ -14,7 +15,10 @@ function setup() {
     todos: List([
       Map({id: uuid.v4(), description: 'Use Redux', completed: false}),
       Map({id: uuid.v4(), description: 'Run the tests', completed: false})
-    ])
+    ]),
+    actions: {
+      editTodo: sinon.spy ()
+    }
   };

   const component = shallow(
(END)

Run the test and watch it pass with the same warning.

  TodoMVC actions
    ✓ Should create an action to add a todo
    ✓ Should create an action to edit a todo

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
Warning: Failed prop type: The prop `editTodo` is marked as required in `TodoItem`, but its value is `undefined`.
    in TodoItem
      ✓ Should be a MainSection component
      ✓ Should include a 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 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

  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


  25 passing (93ms)

Write the code to make the test pass.

diff --git a/src/todomvc/components/MainSection.js b/src/todomvc/components/MainSection.js
index 72bd1e9..b4248af 100644
--- a/src/todomvc/components/MainSection.js
+++ b/src/todomvc/components/MainSection.js
@@ -7,16 +7,17 @@ import TodoItem from './TodoItem'

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

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

     return (
       <section>
         <ul>
-          {props.todos.map(todo => <TodoItem key={todo.get('id')} todo={todo}/>)}
+          {todos.map(todo => <TodoItem key={todo.get('id')} todo={todo} {...actions} />)}
         </ul>
       </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

  Header component
    Should render correctly
      ✓ Should be a Header
      ✓ Should have a title
      ✓ Should have a TodoTextInput field
    Should behave correctly
      ✓ Should call addTodo() if length of text is greater than 0

  MainSection component
    Should render correctly
      ✓ Should be a MainSection component
      ✓ Should include a 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 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

  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


  25 passing (98ms)

Commit changes.

$ git add .
$ git commit -m 'added editTodo action to fix required prop warning'

Run It!

Now let's see what this looks like.

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

Type "Hello" into the text input box and see what happens. Then enter "World" You can see todos appear in your todo list, as well as watch the state update with each action in the Redux Dev Tools window on the right, as before. Now, doubleclick on "Hello" and watch the TodoItem label switch to a TodoTextInput component. Change the value in the TodoTextInput components and watch the state update in the Redux Dev Tools window, like this:

Last updated

Was this helpful?