diff options
Diffstat (limited to 'third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src')
19 files changed, 1221 insertions, 0 deletions
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/actions/index.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/actions/index.js new file mode 100644 index 0000000000..7d023b46df --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/actions/index.js @@ -0,0 +1,8 @@ +import * as types from '../constants/ActionTypes' + +export const addTodo = text => ({ type: types.ADD_TODO, text }) +export const deleteTodo = id => ({ type: types.DELETE_TODO, id }) +export const editTodo = (id, text) => ({ type: types.EDIT_TODO, id, text }) +export const completeTodo = id => ({ type: types.COMPLETE_TODO, id }) +export const completeAll = () => ({ type: types.COMPLETE_ALL }) +export const clearCompleted = () => ({ type: types.CLEAR_COMPLETED }) diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/actions/index.spec.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/actions/index.spec.js new file mode 100644 index 0000000000..06c894c1a5 --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/actions/index.spec.js @@ -0,0 +1,45 @@ +import * as types from '../constants/ActionTypes' +import * as actions from './index' + +describe('todo actions', () => { + it('addTodo should create ADD_TODO action', () => { + expect(actions.addTodo('Use Redux')).toEqual({ + type: types.ADD_TODO, + text: 'Use Redux' + }) + }) + + it('deleteTodo should create DELETE_TODO action', () => { + expect(actions.deleteTodo(1)).toEqual({ + type: types.DELETE_TODO, + id: 1 + }) + }) + + it('editTodo should create EDIT_TODO action', () => { + expect(actions.editTodo(1, 'Use Redux everywhere')).toEqual({ + type: types.EDIT_TODO, + id: 1, + text: 'Use Redux everywhere' + }) + }) + + it('completeTodo should create COMPLETE_TODO action', () => { + expect(actions.completeTodo(1)).toEqual({ + type: types.COMPLETE_TODO, + id: 1 + }) + }) + + it('completeAll should create COMPLETE_ALL action', () => { + expect(actions.completeAll()).toEqual({ + type: types.COMPLETE_ALL + }) + }) + + it('clearCompleted should create CLEAR_COMPLETED action', () => { + expect(actions.clearCompleted()).toEqual({ + type: types.CLEAR_COMPLETED + }) + }) +}) 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() + }) + }) +}) diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/constants/ActionTypes.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/constants/ActionTypes.js new file mode 100644 index 0000000000..a7cdd02d93 --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/constants/ActionTypes.js @@ -0,0 +1,6 @@ +export const ADD_TODO = 'ADD_TODO' +export const DELETE_TODO = 'DELETE_TODO' +export const EDIT_TODO = 'EDIT_TODO' +export const COMPLETE_TODO = 'COMPLETE_TODO' +export const COMPLETE_ALL = 'COMPLETE_ALL' +export const CLEAR_COMPLETED = 'CLEAR_COMPLETED' diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/constants/TodoFilters.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/constants/TodoFilters.js new file mode 100644 index 0000000000..7268785d56 --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/constants/TodoFilters.js @@ -0,0 +1,3 @@ +export const SHOW_ALL = 'show_all' +export const SHOW_COMPLETED = 'show_completed' +export const SHOW_ACTIVE = 'show_active' diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/containers/App.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/containers/App.js new file mode 100644 index 0000000000..8db7c713bd --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/containers/App.js @@ -0,0 +1,31 @@ +import React, { PropTypes } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import Header from '../components/Header' +import MainSection from '../components/MainSection' +import * as TodoActions from '../actions' + +const App = ({todos, actions}) => ( + <div> + <Header addTodo={actions.addTodo} /> + <MainSection todos={todos} actions={actions} /> + </div> +) + +App.propTypes = { + todos: PropTypes.array.isRequired, + actions: PropTypes.object.isRequired +} + +const mapStateToProps = state => ({ + todos: state.todos +}) + +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators(TodoActions, dispatch) +}) + +export default connect( + mapStateToProps, + mapDispatchToProps +)(App) diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/index.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/index.js new file mode 100644 index 0000000000..44f50ab269 --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/index.js @@ -0,0 +1,16 @@ +import React from 'react' +import { render } from 'react-dom' +import { createStore } from 'redux' +import { Provider } from 'react-redux' +import App from './containers/App' +import reducer from './reducers' +import 'todomvc-app-css/index.css' + +const store = createStore(reducer) + +render( + <Provider store={store}> + <App /> + </Provider>, + document.getElementById('root') +) diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/reducers/index.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/reducers/index.js new file mode 100644 index 0000000000..a94ace36b9 --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/reducers/index.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux' +import todos from './todos' + +const rootReducer = combineReducers({ + todos +}) + +export default rootReducer diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/reducers/todos.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/reducers/todos.js new file mode 100644 index 0000000000..84d982664d --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/reducers/todos.js @@ -0,0 +1,49 @@ +import { ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO, COMPLETE_ALL, CLEAR_COMPLETED } from '../constants/ActionTypes' + +const initialState = [] + +export default function todos(state = initialState, action) { + switch (action.type) { + case ADD_TODO: + return [ + ...state, + { + id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1, + completed: false, + text: action.text + } + ] + + case DELETE_TODO: + return state.filter(todo => + todo.id !== action.id + ) + + case EDIT_TODO: + return state.map(todo => + todo.id === action.id ? + { ...todo, text: action.text } : + todo + ) + + case COMPLETE_TODO: + return state.map(todo => + todo.id === action.id ? + { ...todo, completed: !todo.completed } : + todo + ) + + case COMPLETE_ALL: + const areAllMarked = state.every(todo => todo.completed) + return state.map(todo => ({ + ...todo, + completed: !areAllMarked + })) + + case CLEAR_COMPLETED: + return state.filter(todo => todo.completed === false) + + default: + return state + } +} diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/reducers/todos.spec.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/reducers/todos.spec.js new file mode 100644 index 0000000000..1ddded36fb --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/react-redux/src/reducers/todos.spec.js @@ -0,0 +1,284 @@ +import todos from './todos' +import * as types from '../constants/ActionTypes' + +describe('todos reducer', () => { + it('should handle initial state', () => { + expect( + todos(undefined, {}) + ).toEqual([ + { + text: 'Use Redux', + completed: false, + id: 0 + } + ]) + }) + + it('should handle ADD_TODO', () => { + expect( + todos([], { + type: types.ADD_TODO, + text: 'Run the tests' + }) + ).toEqual([ + { + text: 'Run the tests', + completed: false, + id: 0 + } + ]) + + expect( + todos([ + { + text: 'Use Redux', + completed: false, + id: 0 + } + ], { + type: types.ADD_TODO, + text: 'Run the tests' + }) + ).toEqual([ + { + text: 'Run the tests', + completed: false, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ]) + + expect( + todos([ + { + text: 'Run the tests', + completed: false, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ], { + type: types.ADD_TODO, + text: 'Fix the tests' + }) + ).toEqual([ + { + text: 'Fix the tests', + completed: false, + id: 2 + }, { + text: 'Run the tests', + completed: false, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ]) + }) + + it('should handle DELETE_TODO', () => { + expect( + todos([ + { + text: 'Run the tests', + completed: false, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ], { + type: types.DELETE_TODO, + id: 1 + }) + ).toEqual([ + { + text: 'Use Redux', + completed: false, + id: 0 + } + ]) + }) + + it('should handle EDIT_TODO', () => { + expect( + todos([ + { + text: 'Run the tests', + completed: false, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ], { + type: types.EDIT_TODO, + text: 'Fix the tests', + id: 1 + }) + ).toEqual([ + { + text: 'Fix the tests', + completed: false, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ]) + }) + + it('should handle COMPLETE_TODO', () => { + expect( + todos([ + { + text: 'Run the tests', + completed: false, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ], { + type: types.COMPLETE_TODO, + id: 1 + }) + ).toEqual([ + { + text: 'Run the tests', + completed: true, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ]) + }) + + it('should handle COMPLETE_ALL', () => { + expect( + todos([ + { + text: 'Run the tests', + completed: true, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ], { + type: types.COMPLETE_ALL + }) + ).toEqual([ + { + text: 'Run the tests', + completed: true, + id: 1 + }, { + text: 'Use Redux', + completed: true, + id: 0 + } + ]) + + // Unmark if all todos are currently completed + expect( + todos([ + { + text: 'Run the tests', + completed: true, + id: 1 + }, { + text: 'Use Redux', + completed: true, + id: 0 + } + ], { + type: types.COMPLETE_ALL + }) + ).toEqual([ + { + text: 'Run the tests', + completed: false, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ]) + }) + + it('should handle CLEAR_COMPLETED', () => { + expect( + todos([ + { + text: 'Run the tests', + completed: true, + id: 1 + }, { + text: 'Use Redux', + completed: false, + id: 0 + } + ], { + type: types.CLEAR_COMPLETED + }) + ).toEqual([ + { + text: 'Use Redux', + completed: false, + id: 0 + } + ]) + }) + + it('should not generate duplicate ids after CLEAR_COMPLETED', () => { + expect( + [ + { + type: types.COMPLETE_TODO, + id: 0 + }, { + type: types.CLEAR_COMPLETED + }, { + type: types.ADD_TODO, + text: 'Write more tests' + } + ].reduce(todos, [ + { + id: 0, + completed: false, + text: 'Use Redux' + }, { + id: 1, + completed: false, + text: 'Write tests' + } + ]) + ).toEqual([ + { + text: 'Write more tests', + completed: false, + id: 2 + }, { + text: 'Write tests', + completed: false, + id: 1 + } + ]) + }) +}) |