summaryrefslogtreecommitdiffstats
path: root/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components')
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Footer.js71
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Footer.spec.js102
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Header.js25
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Header.spec.js49
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/MainSection.js78
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/MainSection.spec.js129
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoItem.js65
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoItem.spec.js119
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoTextInput.js53
-rw-r--r--third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoTextInput.spec.js80
10 files changed, 771 insertions, 0 deletions
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Footer.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Footer.js
new file mode 100644
index 0000000000..e272c08a54
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Footer.js
@@ -0,0 +1,71 @@
+import React, { PropTypes, Component } from 'react'
+import classnames from 'classnames'
+import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
+
+const FILTER_TITLES = {
+ [SHOW_ALL]: 'All',
+ [SHOW_ACTIVE]: 'Active',
+ [SHOW_COMPLETED]: 'Completed'
+}
+
+export default class Footer extends Component {
+ static propTypes = {
+ completedCount: PropTypes.number.isRequired,
+ activeCount: PropTypes.number.isRequired,
+ filter: PropTypes.string.isRequired,
+ onClearCompleted: PropTypes.func.isRequired,
+ onShow: PropTypes.func.isRequired
+ }
+
+ renderTodoCount() {
+ const { activeCount } = this.props
+ const itemWord = activeCount === 1 ? 'item' : 'items'
+
+ return (
+ <span className="todo-count">
+ <strong>{activeCount || 'No'}</strong> {itemWord} left
+ </span>
+ )
+ }
+
+ renderFilterLink(filter) {
+ const title = FILTER_TITLES[filter]
+ const { filter: selectedFilter, onShow } = this.props
+
+ return (
+ <a className={classnames({ selected: filter === selectedFilter })}
+ style={{ cursor: 'pointer' }}
+ onClick={() => onShow(filter)}>
+ {title}
+ </a>
+ )
+ }
+
+ renderClearButton() {
+ const { completedCount, onClearCompleted } = this.props
+ if (completedCount > 0) {
+ return (
+ <button className="clear-completed"
+ onClick={onClearCompleted} >
+ Clear completed
+ </button>
+ )
+ }
+ }
+
+ render() {
+ return (
+ <footer className="footer">
+ {this.renderTodoCount()}
+ <ul className="filters">
+ {[ SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED ].map(filter =>
+ <li key={filter}>
+ {this.renderFilterLink(filter)}
+ </li>
+ )}
+ </ul>
+ {this.renderClearButton()}
+ </footer>
+ )
+ }
+}
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Footer.spec.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Footer.spec.js
new file mode 100644
index 0000000000..a0fa830e4c
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Footer.spec.js
@@ -0,0 +1,102 @@
+import React from 'react'
+import TestUtils from 'react-addons-test-utils'
+import Footer from './Footer'
+import { SHOW_ALL, SHOW_ACTIVE } from '../constants/TodoFilters'
+
+const setup = propOverrides => {
+ const props = Object.assign({
+ completedCount: 0,
+ activeCount: 0,
+ filter: SHOW_ALL,
+ onClearCompleted: jest.fn(),
+ onShow: jest.fn()
+ }, propOverrides)
+
+ const renderer = TestUtils.createRenderer()
+ renderer.render(<Footer {...props} />)
+ const output = renderer.getRenderOutput()
+
+ return {
+ props: props,
+ output: output
+ }
+}
+
+const getTextContent = elem => {
+ const children = Array.isArray(elem.props.children) ?
+ elem.props.children : [ elem.props.children ]
+
+ return children.reduce((out, child) =>
+ // Concatenate the text
+ // Children are either elements or text strings
+ out + (child.props ? getTextContent(child) : child)
+ , '')
+}
+
+describe('components', () => {
+ describe('Footer', () => {
+ it('should render container', () => {
+ const { output } = setup()
+ expect(output.type).toBe('footer')
+ expect(output.props.className).toBe('footer')
+ })
+
+ it('should display active count when 0', () => {
+ const { output } = setup({ activeCount: 0 })
+ const [ count ] = output.props.children
+ expect(getTextContent(count)).toBe('No items left')
+ })
+
+ it('should display active count when above 0', () => {
+ const { output } = setup({ activeCount: 1 })
+ const [ count ] = output.props.children
+ expect(getTextContent(count)).toBe('1 item left')
+ })
+
+ it('should render filters', () => {
+ const { output } = setup()
+ const [ , filters ] = output.props.children
+ expect(filters.type).toBe('ul')
+ expect(filters.props.className).toBe('filters')
+ expect(filters.props.children.length).toBe(3)
+ filters.props.children.forEach(function checkFilter(filter, i) {
+ expect(filter.type).toBe('li')
+ const a = filter.props.children
+ expect(a.props.className).toBe(i === 0 ? 'selected' : '')
+ expect(a.props.children).toBe({
+ 0: 'All',
+ 1: 'Active',
+ 2: 'Completed'
+ }[i])
+ })
+ })
+
+ it('should call onShow when a filter is clicked', () => {
+ const { output, props } = setup()
+ const [ , filters ] = output.props.children
+ const filterLink = filters.props.children[1].props.children
+ filterLink.props.onClick({})
+ expect(props.onShow).toBeCalledWith(SHOW_ACTIVE)
+ })
+
+ it('shouldnt show clear button when no completed todos', () => {
+ const { output } = setup({ completedCount: 0 })
+ const [ , , clear ] = output.props.children
+ expect(clear).toBe(undefined)
+ })
+
+ it('should render clear button when completed todos', () => {
+ const { output } = setup({ completedCount: 1 })
+ const [ , , clear ] = output.props.children
+ expect(clear.type).toBe('button')
+ expect(clear.props.children).toBe('Clear completed')
+ })
+
+ it('should call onClearCompleted on clear button click', () => {
+ const { output, props } = setup({ completedCount: 1 })
+ const [ , , clear ] = output.props.children
+ clear.props.onClick({})
+ expect(props.onClearCompleted).toBeCalled()
+ })
+ })
+})
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Header.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Header.js
new file mode 100644
index 0000000000..9f192399d9
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Header.js
@@ -0,0 +1,25 @@
+import React, { PropTypes, Component } from 'react'
+import TodoTextInput from './TodoTextInput'
+
+export default class Header extends Component {
+ static propTypes = {
+ addTodo: PropTypes.func.isRequired
+ }
+
+ handleSave = text => {
+ if (text.length !== 0) {
+ this.props.addTodo(text)
+ }
+ }
+
+ render() {
+ return (
+ <header className="header">
+ <h1>todos</h1>
+ <TodoTextInput newTodo
+ onSave={this.handleSave}
+ placeholder="What needs to be done?" />
+ </header>
+ )
+ }
+}
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Header.spec.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Header.spec.js
new file mode 100644
index 0000000000..6b63df2a34
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/Header.spec.js
@@ -0,0 +1,49 @@
+import React from 'react'
+import TestUtils from 'react-addons-test-utils'
+import Header from './Header'
+import TodoTextInput from './TodoTextInput'
+
+const setup = () => {
+ const props = {
+ addTodo: jest.fn()
+ }
+
+ const renderer = TestUtils.createRenderer()
+ renderer.render(<Header {...props} />)
+ const output = renderer.getRenderOutput()
+
+ return {
+ props: props,
+ output: output,
+ renderer: renderer
+ }
+}
+
+describe('components', () => {
+ describe('Header', () => {
+ it('should render correctly', () => {
+ const { output } = setup()
+
+ expect(output.type).toBe('header')
+ expect(output.props.className).toBe('header')
+
+ const [ h1, input ] = output.props.children
+
+ expect(h1.type).toBe('h1')
+ expect(h1.props.children).toBe('todos')
+
+ expect(input.type).toBe(TodoTextInput)
+ expect(input.props.newTodo).toBe(true)
+ expect(input.props.placeholder).toBe('What needs to be done?')
+ })
+
+ it('should call addTodo if length of text is greater than 0', () => {
+ const { output, props } = setup()
+ const input = output.props.children[1]
+ input.props.onSave('')
+ expect(props.addTodo).not.toBeCalled()
+ input.props.onSave('Use Redux')
+ expect(props.addTodo).toBeCalled()
+ })
+ })
+})
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/MainSection.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/MainSection.js
new file mode 100644
index 0000000000..ec23b516b8
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/MainSection.js
@@ -0,0 +1,78 @@
+import React, { Component, PropTypes } from 'react'
+import TodoItem from './TodoItem'
+import Footer from './Footer'
+import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
+
+const TODO_FILTERS = {
+ [SHOW_ALL]: () => true,
+ [SHOW_ACTIVE]: todo => !todo.completed,
+ [SHOW_COMPLETED]: todo => todo.completed
+}
+
+export default class MainSection extends Component {
+ static propTypes = {
+ todos: PropTypes.array.isRequired,
+ actions: PropTypes.object.isRequired
+ }
+
+ state = { filter: SHOW_ALL }
+
+ handleClearCompleted = () => {
+ this.props.actions.clearCompleted()
+ }
+
+ handleShow = filter => {
+ this.setState({ filter })
+ }
+
+ renderToggleAll(completedCount) {
+ const { todos, actions } = this.props
+ if (todos.length > 0) {
+ return (
+ <input className="toggle-all"
+ type="checkbox"
+ checked={completedCount === todos.length}
+ onChange={actions.completeAll} />
+ )
+ }
+ }
+
+ renderFooter(completedCount) {
+ const { todos } = this.props
+ const { filter } = this.state
+ const activeCount = todos.length - completedCount
+
+ if (todos.length) {
+ return (
+ <Footer completedCount={completedCount}
+ activeCount={activeCount}
+ filter={filter}
+ onClearCompleted={this.handleClearCompleted.bind(this)}
+ onShow={this.handleShow.bind(this)} />
+ )
+ }
+ }
+
+ render() {
+ const { todos, actions } = this.props
+ const { filter } = this.state
+
+ const filteredTodos = todos.filter(TODO_FILTERS[filter])
+ const completedCount = todos.reduce((count, todo) =>
+ todo.completed ? count + 1 : count,
+ 0
+ )
+
+ return (
+ <section className="main">
+ {this.renderToggleAll(completedCount)}
+ <ul className="todo-list">
+ {filteredTodos.map(todo =>
+ <TodoItem key={todo.id} todo={todo} {...actions} />
+ )}
+ </ul>
+ {this.renderFooter(completedCount)}
+ </section>
+ )
+ }
+}
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/MainSection.spec.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/MainSection.spec.js
new file mode 100644
index 0000000000..f5924548bb
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/MainSection.spec.js
@@ -0,0 +1,129 @@
+import React from 'react'
+import TestUtils from 'react-addons-test-utils'
+import MainSection from './MainSection'
+import TodoItem from './TodoItem'
+import Footer from './Footer'
+import { SHOW_ALL, SHOW_COMPLETED } from '../constants/TodoFilters'
+
+const setup = propOverrides => {
+ const props = Object.assign({
+ todos: [
+ {
+ text: 'Use Redux',
+ completed: false,
+ id: 0
+ }, {
+ text: 'Run the tests',
+ completed: true,
+ id: 1
+ }
+ ],
+ actions: {
+ editTodo: jest.fn(),
+ deleteTodo: jest.fn(),
+ completeTodo: jest.fn(),
+ completeAll: jest.fn(),
+ clearCompleted: jest.fn()
+ }
+ }, propOverrides)
+
+ const renderer = TestUtils.createRenderer()
+ renderer.render(<MainSection {...props} />)
+ const output = renderer.getRenderOutput()
+
+ return {
+ props: props,
+ output: output,
+ renderer: renderer
+ }
+}
+
+describe('components', () => {
+ describe('MainSection', () => {
+ it('should render container', () => {
+ const { output } = setup()
+ expect(output.type).toBe('section')
+ expect(output.props.className).toBe('main')
+ })
+
+ describe('toggle all input', () => {
+ it('should render', () => {
+ const { output } = setup()
+ const [ toggle ] = output.props.children
+ expect(toggle.type).toBe('input')
+ expect(toggle.props.type).toBe('checkbox')
+ expect(toggle.props.checked).toBe(false)
+ })
+
+ it('should be checked if all todos completed', () => {
+ const { output } = setup({ todos: [
+ {
+ text: 'Use Redux',
+ completed: true,
+ id: 0
+ }
+ ]
+ })
+ const [ toggle ] = output.props.children
+ expect(toggle.props.checked).toBe(true)
+ })
+
+ it('should call completeAll on change', () => {
+ const { output, props } = setup()
+ const [ toggle ] = output.props.children
+ toggle.props.onChange({})
+ expect(props.actions.completeAll).toBeCalled()
+ })
+ })
+
+ describe('footer', () => {
+ it('should render', () => {
+ const { output } = setup()
+ const [ , , footer ] = output.props.children
+ expect(footer.type).toBe(Footer)
+ expect(footer.props.completedCount).toBe(1)
+ expect(footer.props.activeCount).toBe(1)
+ expect(footer.props.filter).toBe(SHOW_ALL)
+ })
+
+ it('onShow should set the filter', () => {
+ const { output, renderer } = setup()
+ const [ , , footer ] = output.props.children
+ footer.props.onShow(SHOW_COMPLETED)
+ const updated = renderer.getRenderOutput()
+ const [ , , updatedFooter ] = updated.props.children
+ expect(updatedFooter.props.filter).toBe(SHOW_COMPLETED)
+ })
+
+ it('onClearCompleted should call clearCompleted', () => {
+ const { output, props } = setup()
+ const [ , , footer ] = output.props.children
+ footer.props.onClearCompleted()
+ expect(props.actions.clearCompleted).toBeCalled()
+ })
+ })
+
+ describe('todo list', () => {
+ it('should render', () => {
+ const { output, props } = setup()
+ const [ , list ] = output.props.children
+ expect(list.type).toBe('ul')
+ expect(list.props.children.length).toBe(2)
+ list.props.children.forEach((item, i) => {
+ expect(item.type).toBe(TodoItem)
+ expect(item.props.todo).toBe(props.todos[i])
+ })
+ })
+
+ it('should filter items', () => {
+ const { output, renderer, props } = setup()
+ const [ , , footer ] = output.props.children
+ footer.props.onShow(SHOW_COMPLETED)
+ const updated = renderer.getRenderOutput()
+ const [ , updatedList ] = updated.props.children
+ expect(updatedList.props.children.length).toBe(1)
+ expect(updatedList.props.children[0].props.todo).toBe(props.todos[1])
+ })
+ })
+ })
+})
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoItem.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoItem.js
new file mode 100644
index 0000000000..490921d543
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoItem.js
@@ -0,0 +1,65 @@
+import React, { Component, PropTypes } from 'react'
+import classnames from 'classnames'
+import TodoTextInput from './TodoTextInput'
+
+export default class TodoItem extends Component {
+ static propTypes = {
+ todo: PropTypes.object.isRequired,
+ editTodo: PropTypes.func.isRequired,
+ deleteTodo: PropTypes.func.isRequired,
+ completeTodo: PropTypes.func.isRequired
+ }
+
+ state = {
+ editing: false
+ }
+
+ handleDoubleClick = () => {
+ this.setState({ editing: true })
+ }
+
+ handleSave = (id, text) => {
+ if (text.length === 0) {
+ this.props.deleteTodo(id)
+ } else {
+ this.props.editTodo(id, text)
+ }
+ this.setState({ editing: false })
+ }
+
+ render() {
+ const { todo, completeTodo, deleteTodo } = this.props
+
+ let element
+ if (this.state.editing) {
+ element = (
+ <TodoTextInput text={todo.text}
+ editing={this.state.editing}
+ onSave={(text) => this.handleSave(todo.id, text)} />
+ )
+ } else {
+ element = (
+ <div className="view">
+ <input className="toggle"
+ type="checkbox"
+ checked={todo.completed}
+ onChange={() => completeTodo(todo.id)} />
+ <label onDoubleClick={this.handleDoubleClick}>
+ {todo.text}
+ </label>
+ <button className="destroy"
+ onClick={() => deleteTodo(todo.id)} />
+ </div>
+ )
+ }
+
+ return (
+ <li className={classnames({
+ completed: todo.completed,
+ editing: this.state.editing
+ })}>
+ {element}
+ </li>
+ )
+ }
+}
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoItem.spec.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoItem.spec.js
new file mode 100644
index 0000000000..4c364f5b19
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoItem.spec.js
@@ -0,0 +1,119 @@
+import React from 'react'
+import TestUtils from 'react-addons-test-utils'
+import TodoItem from './TodoItem'
+import TodoTextInput from './TodoTextInput'
+
+const setup = ( editing = false ) => {
+ const props = {
+ todo: {
+ id: 0,
+ text: 'Use Redux',
+ completed: false
+ },
+ editTodo: jest.fn(),
+ deleteTodo: jest.fn(),
+ completeTodo: jest.fn()
+ }
+
+ const renderer = TestUtils.createRenderer()
+
+ renderer.render(
+ <TodoItem {...props} />
+ )
+
+ let output = renderer.getRenderOutput()
+
+ if (editing) {
+ const label = output.props.children.props.children[1]
+ label.props.onDoubleClick({})
+ output = renderer.getRenderOutput()
+ }
+
+ return {
+ props: props,
+ output: output,
+ renderer: renderer
+ }
+}
+
+describe('components', () => {
+ describe('TodoItem', () => {
+ it('initial render', () => {
+ const { output } = setup()
+
+ expect(output.type).toBe('li')
+ expect(output.props.className).toBe('')
+
+ const div = output.props.children
+
+ expect(div.type).toBe('div')
+ expect(div.props.className).toBe('view')
+
+ const [ input, label, button ] = div.props.children
+
+ expect(input.type).toBe('input')
+ expect(input.props.checked).toBe(false)
+
+ expect(label.type).toBe('label')
+ expect(label.props.children).toBe('Use Redux')
+
+ expect(button.type).toBe('button')
+ expect(button.props.className).toBe('destroy')
+ })
+
+ it('input onChange should call completeTodo', () => {
+ const { output, props } = setup()
+ const input = output.props.children.props.children[0]
+ input.props.onChange({})
+ expect(props.completeTodo).toBeCalledWith(0)
+ })
+
+ it('button onClick should call deleteTodo', () => {
+ const { output, props } = setup()
+ const button = output.props.children.props.children[2]
+ button.props.onClick({})
+ expect(props.deleteTodo).toBeCalledWith(0)
+ })
+
+ it('label onDoubleClick should put component in edit state', () => {
+ const { output, renderer } = setup()
+ const label = output.props.children.props.children[1]
+ label.props.onDoubleClick({})
+ const updated = renderer.getRenderOutput()
+ expect(updated.type).toBe('li')
+ expect(updated.props.className).toBe('editing')
+ })
+
+ it('edit state render', () => {
+ const { output } = setup(true)
+
+ expect(output.type).toBe('li')
+ expect(output.props.className).toBe('editing')
+
+ const input = output.props.children
+ expect(input.type).toBe(TodoTextInput)
+ expect(input.props.text).toBe('Use Redux')
+ expect(input.props.editing).toBe(true)
+ })
+
+ it('TodoTextInput onSave should call editTodo', () => {
+ const { output, props } = setup(true)
+ output.props.children.props.onSave('Use Redux')
+ expect(props.editTodo).toBeCalledWith(0, 'Use Redux')
+ })
+
+ it('TodoTextInput onSave should call deleteTodo if text is empty', () => {
+ const { output, props } = setup(true)
+ output.props.children.props.onSave('')
+ expect(props.deleteTodo).toBeCalledWith(0)
+ })
+
+ it('TodoTextInput onSave should exit component from edit state', () => {
+ const { output, renderer } = setup(true)
+ output.props.children.props.onSave('Use Redux')
+ const updated = renderer.getRenderOutput()
+ expect(updated.type).toBe('li')
+ expect(updated.props.className).toBe('')
+ })
+ })
+})
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoTextInput.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoTextInput.js
new file mode 100644
index 0000000000..cc09a3b0bb
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoTextInput.js
@@ -0,0 +1,53 @@
+import React, { Component, PropTypes } from 'react'
+import classnames from 'classnames'
+
+export default class TodoTextInput extends Component {
+ static propTypes = {
+ onSave: PropTypes.func.isRequired,
+ text: PropTypes.string,
+ placeholder: PropTypes.string,
+ editing: PropTypes.bool,
+ newTodo: PropTypes.bool
+ }
+
+ state = {
+ text: this.props.text || ''
+ }
+
+ handleSubmit = e => {
+ const text = e.target.value.trim()
+ if (e.which === 13) {
+ this.props.onSave(text)
+ if (this.props.newTodo) {
+ this.setState({ text: '' })
+ }
+ }
+ }
+
+ handleChange = e => {
+ this.setState({ text: e.target.value })
+ }
+
+ handleBlur = e => {
+ if (!this.props.newTodo) {
+ this.props.onSave(e.target.value)
+ }
+ }
+
+ render() {
+ return (
+ <input className={
+ classnames({
+ edit: this.props.editing,
+ 'new-todo': this.props.newTodo
+ })}
+ type="text"
+ placeholder={this.props.placeholder}
+ autoFocus="true"
+ value={this.state.text}
+ onBlur={this.handleBlur}
+ onChange={this.handleChange}
+ onKeyDown={this.handleSubmit} />
+ )
+ }
+}
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoTextInput.spec.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoTextInput.spec.js
new file mode 100644
index 0000000000..efca79a908
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/components/TodoTextInput.spec.js
@@ -0,0 +1,80 @@
+import React from 'react'
+import TestUtils from 'react-addons-test-utils'
+import TodoTextInput from './TodoTextInput'
+
+const setup = propOverrides => {
+ const props = Object.assign({
+ onSave: jest.fn(),
+ text: 'Use Redux',
+ placeholder: 'What needs to be done?',
+ editing: false,
+ newTodo: false
+ }, propOverrides)
+
+ const renderer = TestUtils.createRenderer()
+
+ renderer.render(
+ <TodoTextInput {...props} />
+ )
+
+ const output = renderer.getRenderOutput()
+
+ return {
+ props: props,
+ output: output,
+ renderer: renderer
+ }
+}
+
+describe('components', () => {
+ describe('TodoTextInput', () => {
+ it('should render correctly', () => {
+ const { output } = setup()
+ expect(output.props.placeholder).toEqual('What needs to be done?')
+ expect(output.props.value).toEqual('Use Redux')
+ expect(output.props.className).toEqual('')
+ })
+
+ it('should render correctly when editing=true', () => {
+ const { output } = setup({ editing: true })
+ expect(output.props.className).toEqual('edit')
+ })
+
+ it('should render correctly when newTodo=true', () => {
+ const { output } = setup({ newTodo: true })
+ expect(output.props.className).toEqual('new-todo')
+ })
+
+ it('should update value on change', () => {
+ const { output, renderer } = setup()
+ output.props.onChange({ target: { value: 'Use Radox' } })
+ const updated = renderer.getRenderOutput()
+ expect(updated.props.value).toEqual('Use Radox')
+ })
+
+ it('should call onSave on return key press', () => {
+ const { output, props } = setup()
+ output.props.onKeyDown({ which: 13, target: { value: 'Use Redux' } })
+ expect(props.onSave).toBeCalledWith('Use Redux')
+ })
+
+ it('should reset state on return key press if newTodo', () => {
+ const { output, renderer } = setup({ newTodo: true })
+ output.props.onKeyDown({ which: 13, target: { value: 'Use Redux' } })
+ const updated = renderer.getRenderOutput()
+ expect(updated.props.value).toEqual('')
+ })
+
+ it('should call onSave on blur', () => {
+ const { output, props } = setup()
+ output.props.onBlur({ target: { value: 'Use Redux' } })
+ expect(props.onSave).toBeCalledWith('Use Redux')
+ })
+
+ it('shouldnt call onSave on blur if newTodo', () => {
+ const { output, props } = setup({ newTodo: true })
+ output.props.onBlur({ target: { value: 'Use Redux' } })
+ expect(props.onSave).not.toBeCalled()
+ })
+ })
+})