Skip to content

Commit 2f69bc5

Browse files
committed
feat: prototype commit
1 parent 3045432 commit 2f69bc5

18 files changed

+429
-14
lines changed

app/components/AddTodo.jsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
import todoStore from '../stores/todoStore';
3+
4+
export default class AddTodo extends React.Component {
5+
addTodo() {
6+
// e.preventDefault();
7+
const newTodoName = this.refs.todoTitle.value;
8+
if (newTodoName) {
9+
todoStore.addNewTodo({
10+
name: newTodoName
11+
});
12+
todoStore.emitChange();
13+
this.refs.todoTitle.value = '';
14+
}
15+
}
16+
17+
render() {
18+
return (
19+
<div className="add-todo">
20+
<input type="text" placeholder="Todo four" ref="todoTitle" />
21+
<button className="add-button" onClick={this.addTodo.bind(this)}>
22+
Add Todo
23+
</button>
24+
</div>
25+
);
26+
}
27+
}

app/components/App.css

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
h1{
2-
color: red;
3-
}
1+

app/components/App.jsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import React from 'react';
2-
3-
require('./App.css');
2+
import AddTodo from './AddTodo';
3+
import TodoList from './TodoList';
44

55
export default class App extends React.Component {
6-
constructor(props) {
7-
super(props);
8-
}
96

107
render() {
118
return (
12-
<h1>Hello World</h1>
9+
<div>
10+
<h1>Todos</h1>
11+
<TodoList/>
12+
<AddTodo/>
13+
</div>
1314
);
1415
}
1516
}

app/components/TodoItem.jsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
import todoStore from '../stores/todoStore';
3+
4+
export default class Todo extends React.Component {
5+
toggleDone(e) {
6+
e.preventDefault();
7+
todoStore.toggleDone(this.props.todo.id);
8+
todoStore.emitChange();
9+
}
10+
11+
deleteTodo(e) {
12+
e.preventDefault();
13+
todoStore.deleteTodo(this.props.todo.id);
14+
todoStore.emitChange();
15+
}
16+
17+
render() {
18+
const todo = this.props.todo;
19+
const todoDone = todo.done ? 'todo-done' : '';
20+
return (
21+
<li>
22+
<span className={`todo-text ${todoDone}`} onClick={this.toggleDone.bind(this)}>{todo.name}</span>
23+
<button className="delete" onClick={this.deleteTodo.bind(this)}> x </button>
24+
</li>
25+
);
26+
}
27+
}

app/components/TodoList.jsx

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import todoStore from '../stores/todoStore';
3+
import TodoItem from './TodoItem';
4+
5+
export default class TodoList extends React.Component {
6+
constructor(props) {
7+
super(props);
8+
this.state = todoStore.getAll();
9+
}
10+
11+
componentDidMount() {
12+
todoStore.addChangeListener(this._onChange.bind(this));
13+
}
14+
15+
componentWillUnmount() {
16+
todoStore.removeChangeListener(this._onChange.bind(this));
17+
}
18+
19+
_onChange() {
20+
this.setState(todoStore.getAll());
21+
}
22+
23+
render() {
24+
const TodoItemList = this.state.todos.map(todo => {
25+
return (
26+
<TodoItem key={todo.id} todo={todo}/>
27+
);
28+
});
29+
return (
30+
<ul>{TodoItemList}</ul>
31+
);
32+
}
33+
}

app/components/state-functions.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export function toggleDone(state, id) {
2+
const todos = state.todos.map((todo) => {
3+
if (todo.id === id) {
4+
todo.done = !todo.done;
5+
}
6+
7+
return todo;
8+
});
9+
10+
return { todos };
11+
}
12+
13+
export function addTodo(state, todo) {
14+
if (state.todos.length !== 0) {
15+
const lastTodo = state.todos[state.todos.length - 1];
16+
todo.id = lastTodo.id + 1;
17+
} else {
18+
todo.id = 0;
19+
}
20+
todo.done = false;
21+
22+
return {
23+
todos: state.todos.concat([todo])
24+
};
25+
}
26+
27+
export function deleteTodo(state, id) {
28+
return {
29+
todos: state.todos.filter((todo) => todo.id !== id)
30+
};
31+
}

app/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<html>
22
<link rel="stylesheet" type="text/css" href="main.css">
3-
<title>React-Babel-Webpack Boileplate</title>
3+
<title>Enzyme Demo</title>
44
<body>
55
<script type="text/javascript" src="bundle.js"></script>
66
</body>

app/main.css

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
h1 {
2+
color: red;
3+
}
4+
5+
li span.todo-done {
6+
text-decoration: line-through;
7+
}
8+
9+
li span.todo-text {
10+
display: inline-block;
11+
width: 6em;
12+
text-overflow: ellipsis;
13+
/* Required for text-overflow to do anything */
14+
white-space: nowrap;
15+
overflow: hidden;
16+
}

app/stores/todoStore.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {EventEmitter} from 'events';
2+
import assign from 'object-assign';
3+
4+
const todoStore = assign({}, EventEmitter.prototype, {
5+
items: {
6+
todos: [
7+
{ id: 0, name: 'Todo one', done: false },
8+
{ id: 1, name: 'Todo two', done: false },
9+
{ id: 2, name: 'Todo three', done: false },
10+
]
11+
},
12+
13+
nextId: 3,
14+
15+
getAll: function getAll() {
16+
return this.items;
17+
},
18+
19+
emitChange: function emitChange() {
20+
this.emit('change');
21+
},
22+
23+
addChangeListener: function addChangeListener(callback) {
24+
this.on('change', callback);
25+
},
26+
27+
removeChangeListener: function removeChangeListener(callback) {
28+
this.removeListener('change', callback);
29+
},
30+
31+
addNewTodo: function addNewTodo(todo) {
32+
const todos = this.items.todos;
33+
if (!todos || typeof this.items.todos.length !== 'number') {
34+
this.items.todos = [];
35+
}
36+
todo.id = this.nextId++;
37+
todo.done = false;
38+
this.items.todos.push(todo);
39+
},
40+
41+
toggleDone: function toggleDone(id) {
42+
this.items.todos = this.items.todos.map(todo => {
43+
if (todo.id === id) {
44+
todo.done = !todo.done;
45+
}
46+
return todo;
47+
});
48+
},
49+
50+
deleteTodo: function deleteTodo(id) {
51+
this.items.todos = this.items.todos.filter((todo) => todo.id !== id);
52+
}
53+
});
54+
55+
export default todoStore;

package.json

+11-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "app/main.jsx",
66
"scripts": {
77
"lint": "eslint 'app/**/*.@(js|jsx)'",
8-
"test": "echo \"Error: no test specified\" && exit 1",
8+
"test": "mocha --compilers js:babel-core/register --require ./test/setup.js",
99
"build": "webpack",
1010
"start": "webpack-dev-server --devtool eval --progress --hot --colors --content-base app",
1111
"deploy": "NODE_ENV=production webpack -p --config webpack.production.config.js",
@@ -17,23 +17,30 @@
1717
"react-dom": "~0.14.3"
1818
},
1919
"devDependencies": {
20-
"webpack": "~1.12.9",
21-
"webpack-dev-server": "~1.14.0",
2220
"babel-core": "~6.2.1",
2321
"babel-eslint": "~4.1.6",
2422
"babel-loader": "~6.2.0",
2523
"babel-plugin-transform-runtime": "~6.1.18",
2624
"babel-preset-es2015": "~6.1.18",
2725
"babel-preset-react": "~6.1.18",
2826
"babel-preset-stage-0": "~6.1.18",
27+
"chai": "^3.4.1",
2928
"copy-webpack-plugin": "~0.3.3",
3029
"css-loader": "~0.23.0",
30+
"enzyme": "^1.4.1",
3131
"eslint": "~1.10.1",
3232
"eslint-config-airbnb": "~1.0.0",
3333
"eslint-plugin-react": "~3.10.0",
34+
"jsdom": "^7.2.2",
35+
"jsx-test": "^0.8.2",
36+
"mocha": "^2.3.4",
37+
"object-assign": "^4.0.1",
3438
"open-browser-webpack-plugin": "0.0.1",
3539
"precommit-hook": "~3.0.0",
36-
"style-loader": "~0.13.0"
40+
"react-addons-test-utils": "^0.14.6",
41+
"style-loader": "~0.13.0",
42+
"webpack": "~1.12.9",
43+
"webpack-dev-server": "~1.14.0"
3744
},
3845
"keywords": [
3946
"es6",

test/dom1.test.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import jsdom from 'jsdom';
3+
import { expect } from 'chai';
4+
import TestUtils from 'react-addons-test-utils';
5+
import TodoList from '../app/components/TodoList';
6+
7+
function setupDom() {
8+
if (typeof document !== 'undefined') {
9+
return;
10+
}
11+
12+
global.document = jsdom.jsdom('<html><body></body></html>');
13+
global.window = document.defaultView;
14+
global.navigator = window.navigator;
15+
};
16+
17+
describe('模拟DOM测试', function (done) {
18+
it('点击删除按钮,删除Todo', function () {
19+
setupDom();
20+
const todoList = TestUtils.renderIntoDocument(
21+
<TodoList/>
22+
);
23+
let todoItems = TestUtils.scryRenderedDOMComponentsWithTag(todoList, 'li');
24+
let todoLength = todoItems.length;
25+
let deleteButton = todoItems[0].querySelector('button');
26+
TestUtils.Simulate.click(deleteButton);
27+
let todoItemsAfterClick = TestUtils.scryRenderedDOMComponentsWithTag(todoList, 'li');
28+
expect(todoItemsAfterClick.length).to.be.equal(todoLength - 1);
29+
});
30+
});
31+
32+

test/dom2.test.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import jsdom from 'jsdom';
3+
import TestUtils from 'react-addons-test-utils';
4+
import { expect } from 'chai';
5+
6+
import TodoList from '../app/components/TodoList';
7+
8+
function setupDom() {
9+
if (typeof document !== 'undefined') {
10+
return;
11+
}
12+
13+
global.document = jsdom.jsdom('<html><body></body></html>');
14+
global.window = document.defaultView;
15+
global.navigator = window.navigator;
16+
};
17+
18+
describe('模拟DOM测试', function (done) {
19+
it('点击Todo,改变完成状态', function () {
20+
setupDom();
21+
const todoList = TestUtils.renderIntoDocument(
22+
<TodoList/>
23+
);
24+
const todoItems = TestUtils.scryRenderedDOMComponentsWithTag(todoList, 'li');
25+
const todoText = todoItems[0].querySelector('span');
26+
let isDone = todoText.classList.contains('todo-done');
27+
TestUtils.Simulate.click(todoText);
28+
expect(todoText.classList.contains('todo-done')).to.be.equal(!isDone);
29+
});
30+
});
31+
32+

test/dom3.test.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import jsdom from 'jsdom';
3+
import { expect } from 'chai';
4+
import TestUtils from 'react-addons-test-utils';
5+
6+
import App from '../app/components/App';
7+
import AddTodo from '../app/components/AddTodo';
8+
require('react/node_modules/fbjs/lib/ExecutionEnvironment').canUseDOM = true;
9+
function setupDom() {
10+
// if (typeof document !== 'undefined') {
11+
// return;
12+
// }
13+
14+
global.document = jsdom.jsdom('<html><body></body></html>');
15+
global.window = document.defaultView;
16+
global.navigator = global.window.navigator;
17+
};
18+
// setupDom();
19+
20+
describe('模拟DOM测试', function (done) {
21+
it('添加新的Todo', function () {
22+
const app = TestUtils.renderIntoDocument(
23+
<App/>
24+
);
25+
let todoItems = TestUtils.scryRenderedDOMComponentsWithClass(app, 'todo-text');
26+
let todoItemsLength = todoItems.length;
27+
let addTodo = TestUtils.findRenderedDOMComponentWithClass(app, 'add-todo');
28+
// let addTodo = TestUtils.findRenderedComponentWithType(app, AddTodo);
29+
let addButton = addTodo.querySelector('button');
30+
// let addButton = TestUtils.scryRenderedDOMComponentsWithTag(app, 'button')[3];
31+
// let addInput = addTodo.querySelector('input');
32+
let addInput = TestUtils.findRenderedDOMComponentWithTag(app, 'input');
33+
addInput.value = '4th Todo';
34+
// TestUtils.Simulate.change(addInput);
35+
// TestUtils.Simulate.keyDown(addInput, {key: "Enter", keyCode: 13, which: 13});
36+
TestUtils.Simulate.click(addButton);
37+
let todoItemsAfterClick = TestUtils.scryRenderedDOMComponentsWithClass(app, 'todo-text');
38+
expect(todoItemsAfterClick.length).to.be.equal(todoItemsLength + 1);
39+
});
40+
});
41+
42+

0 commit comments

Comments
 (0)