Skip to content

Commit e62adc8

Browse files
committed
test for props overwrite
1 parent 4393cd1 commit e62adc8

File tree

4 files changed

+153
-107
lines changed

4 files changed

+153
-107
lines changed

lib/__tests__/react-most-test.jsx

Lines changed: 118 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,103 +2,136 @@ import React from 'react';
22
import ReactDOM from 'react-dom';
33
import TestUtils from 'react-addons-test-utils';
44
import * as most from 'most';
5-
let {default: Most, connect} = require('../react-most');
6-
let {do$, historyStreamOf} = require('../test-utils')
5+
import Most, {connect} from '../react-most';
6+
import {do$, historyStreamOf, intentStreamOf} from '../test-utils'
77

8-
describe('mostux', () => {
9-
describe('todo reactive', ()=>{
10-
let todolist;
11-
let Todo = React.createClass({
12-
render(){
13-
return <div className={'todo-'+this.props.todo.id} key={this.props.todo.id} data-complete={this.props.todo.done}>{this.props.todo.text}</div>
14-
}
15-
});
8+
const CounterView = props=> (
9+
<div>
10+
<span className="count">{props.count}</span>
11+
<span className="wrapperProps">{props.wrapperProps}</span>
12+
<span className="overwritedProps">{props.overwritedProps}</span>
13+
</div>
14+
)
1615

17-
let TodoList = React.createClass({
18-
render(){
19-
let {todos} = this.props;
20-
let todoElements;
21-
if(todos){
22-
todoElements = todos.map(todo => {
23-
return <Todo key={todo.id} todo={todo} />
24-
});
25-
}
26-
return <div>
27-
{todoElements}
28-
</div>
16+
CounterView.defaultProps = {count: 0, overwritedProps: 'inner'}
17+
18+
const Counter = connect(intent$=>{
19+
return {
20+
sink$: intent$.map(intent=>{
21+
switch(intent.type) {
22+
case 'inc': return state=>({count:state.count+1})
23+
case 'dec':
24+
intent$.send({type:'dec triggered'})
25+
return state=>({count:state.count-1})
26+
case 'changeWrapperProps':
27+
return state=>({wrapperProps: intent.value,
28+
overwritedProps: intent.value})
29+
case 'changeDefaultProps':
30+
return state=>({count: intent.value})
31+
default:
32+
return state=>state
2933
}
30-
});
34+
}),
35+
inc: ()=>({type:'inc'}),
36+
dec: ()=>({type:'dec'}),
37+
changeWrapperProps: (value)=>({type:'changeWrapperProps', value}),
38+
changeDefaultProps: (value)=>({type:'changeDefaultProps', value}),
39+
}
40+
})(CounterView)
3141

32-
let RxTodoList = connect(function(intent$){
33-
let default$ = most.of(()=>({
34-
todos: [
35-
{id:1, text:5, done:false},
36-
{id:2, text:'heheda', done:false},
37-
]
38-
}))
39-
let done$ = intent$.filter(x=>x.type=='done');
40-
let delete$ = intent$.filter(x=>x.type=='remove');
41-
let doneState$ = done$.map((done)=>{
42-
return state=>(
43-
{
44-
todos:state.todos.map(todo=>{
45-
if(todo.id==done.id){
46-
todo.done =! todo.done;
47-
return todo;
48-
}
49-
return todo;
50-
})
51-
}
52-
)
53-
});
42+
describe('react-most', () => {
43+
describe('actions', ()=>{
44+
it('add intent to intent$ and go through sink$', ()=> {
45+
let counterWrapper = TestUtils.renderIntoDocument(
46+
<Most >
47+
<Counter history={true} />
48+
</Most>
49+
)
50+
let counter = TestUtils.findRenderedComponentWithType(counterWrapper, Counter)
5451

55-
let deleteState$ = delete$.map((deleted)=>{
56-
return state=>(
57-
{todos: state.todos.filter(todo=>todo.id!=deleted.id)}
58-
)
59-
});
60-
return {
61-
done: (id)=>({type:'done', id}),
62-
remove: function remove(id){return {type:'remove', id}},
63-
default$,
64-
deleteState$,
65-
doneState$,
66-
}
67-
})(TodoList);
52+
do$([()=>counter.actions.inc(),
53+
()=>counter.actions.inc()])
6854

69-
it('render dump Component TodoList UI correctly', () => {
70-
let todolist = TestUtils.renderIntoDocument(
71-
<TodoList todos={[
72-
{id:1, text:5, done:false},
73-
{id:2, text:'heheda', done:false},
74-
]}/>
55+
return historyStreamOf(counter)
56+
.take$(2)
57+
.then(state=>expect(state.count).toEqual(2))
58+
})
59+
60+
it('sink can also generate intent', ()=> {
61+
let counterWrapper = TestUtils.renderIntoDocument(
62+
<Most >
63+
<Counter history={true} />
64+
</Most>
7565
)
76-
let todos = TestUtils.scryRenderedComponentsWithType(todolist, Todo)
77-
expect(todos.length).toBe(2);
78-
expect(todos[0].props.todo.done).toBe(false);
79-
});
66+
let counter = TestUtils.findRenderedComponentWithType(counterWrapper, Counter)
67+
68+
do$([()=>counter.actions.dec()])
69+
70+
return intentStreamOf(counter)
71+
.take(2).observe(_=>_)
72+
.then(intent=>expect(intent.type).toEqual('dec triggered'))
73+
})
8074

81-
it('behaviors connected to dump Component works as expected', ()=> {
82-
let todolist = TestUtils.renderIntoDocument(
75+
it('props will overwirte components default props', ()=>{
76+
let counterWrapper = TestUtils.renderIntoDocument(
8377
<Most >
84-
<RxTodoList history={true}>
85-
</RxTodoList>
78+
<Counter count={9} history={true} />
8679
</Most>
8780
)
88-
let div = TestUtils.findRenderedComponentWithType(todolist, RxTodoList)
81+
let counter = TestUtils.findRenderedComponentWithType(counterWrapper, Counter)
82+
83+
do$([()=>counter.actions.inc()])
84+
return historyStreamOf(counter)
85+
.take$(1)
86+
.then(state=>expect(state.count).toBe(10))
87+
})
88+
89+
it('props that not overlap with views defaultProps can not be changed', ()=>{
90+
let CounterWrapper = React.createClass({
91+
getInitialState(){
92+
return {
93+
wrapperProps: 'heheda',
94+
overwritedProps: 'hoho',
95+
count: 0,
96+
}
97+
},
98+
render(){
99+
return <Counter
100+
count={this.state.count}
101+
wrapperProps={this.state.wrapperProps}
102+
overwritedProps={this.state.overwritedProps}
103+
history={true} />
104+
}
89105

90-
do$([()=>div.actions.done(1),
91-
()=>div.actions.done(2),
92-
()=>div.actions.remove(2),
93-
()=>div.actions.done(1)])
106+
})
107+
let counterMostWrapper = TestUtils.renderIntoDocument(
108+
<Most >
109+
<CounterWrapper />
110+
</Most>
111+
)
112+
let counterWrapper = TestUtils.findRenderedComponentWithType(counterMostWrapper, CounterWrapper)
113+
let counter = TestUtils.findRenderedComponentWithType(counterWrapper, Counter)
94114

95-
return historyStreamOf(div)
96-
.take$(4)
97-
.then(state=>
98-
expect(state.todos).toEqual(
99-
[
100-
{id: 1, text: 5, done: false}
101-
]))
102-
});
115+
return do$([
116+
()=>counter.actions.changeWrapperProps('miao'),
117+
()=>counter.actions.changeDefaultProps(19)
118+
]).then(()=>{
119+
let wrapperProps = TestUtils.findRenderedDOMComponentWithClass(counter, 'wrapperProps')
120+
let overwritedProps = TestUtils.findRenderedDOMComponentWithClass(counter, 'overwritedProps')
121+
let count = TestUtils.findRenderedDOMComponentWithClass(counter, 'count')
122+
expect(counter.props.wrapperProps).toBe('heheda')
123+
expect(wrapperProps.textContent).toBe('miao')
124+
expect(overwritedProps.textContent).toBe('miao')
125+
expect(count.textContent).toBe('19')
126+
return do$([
127+
()=>counterWrapper.setState({overwritedProps: 'wrapper', count: 1})
128+
])
129+
}).then(()=>{
130+
let overwritedProps = TestUtils.findRenderedDOMComponentWithClass(counter, 'overwritedProps')
131+
let count = TestUtils.findRenderedDOMComponentWithClass(counter, 'count')
132+
expect(overwritedProps.textContent).toBe('wrapper')
133+
expect(count.textContent).toBe('1')
134+
})
135+
})
103136
});
104137
})

lib/react-most.js

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React from 'react'
22
import initHistory from './history'
33
import mostEngine from './engine/most'
44
import mergeAll from 'ramda/src/mergeAll'
5+
import pick from 'ramda/src/pick'
6+
import keys from 'ramda/src/keys'
57
// unfortunately React doesn't support symbol as context key yet, so let me just preteding using Symbol until react implement the Symbol version of Object.assign
68
const intentStream = "__reactive.react.intentStream__";
79
const historyStream = "__reactive.react.historyStream__";
@@ -19,41 +21,42 @@ function observable(obj){
1921

2022
export function connect(main, initprops={}) {
2123
return function(ReactClass){
22-
class Connect extends React.Component {
24+
class Connect extends React.PureComponent {
2325
constructor(props, context) {
2426
super(props, context);
25-
// the outer props has higher piority
26-
this.state = mergeAll([ReactClass.defaultProps, initprops, props])
27-
initprops = {}
28-
if(props.history) initprops.history=true
29-
this.actions = {
30-
fromEvent(e){
31-
let {type, target} = e;
32-
context[intentStream].send({type,target});
27+
let _actions = {
28+
fromEvent(e, f=x=>x){
29+
context[intentStream].send(f(e));
3330
},
3431
fromPromise(p){
3532
p.then(context[intentStream].send);
3633
}
3734
};
3835
let sinks = main(context[intentStream],props);
39-
let actionsSinks = []
40-
if(initprops.history){
36+
let _actionsSinks = []
37+
if(initprops.history || props.history){
4138
initprops.history = initHistory(context[historyStream])
4239
initprops.history.travel.observe(state=>{
4340
return this.setState(state)
4441
})
4542
}
4643
for(let name in sinks){
4744
if(observable(sinks[name])){
48-
actionsSinks.push(sinks[name]);
45+
_actionsSinks.push(sinks[name]);
4946
}
5047
else if(sinks[name] instanceof Function){
51-
this.actions[name] = (...args)=>{
48+
_actions[name] = (...args)=>{
5249
return this.context[intentStream].send(sinks[name].apply(null, args));
5350
}
5451
}
5552
}
56-
this.actionsSinks = actionsSinks;
53+
this.actionsSinks = _actionsSinks;
54+
let defaultKey = keys(ReactClass.defaultProps)
55+
this.state = mergeAll([ReactClass.defaultProps, pick(defaultKey, props)])
56+
this.actions = mergeAll([_actions, props.actions])
57+
}
58+
componentWillReceiveProps(nextProps){
59+
this.setState(state=>pick(keys(state), nextProps))
5760
}
5861
componentDidMount(){
5962
this.context[flatObserve](this.actionsSinks, (action)=>{
@@ -71,7 +74,7 @@ export function connect(main, initprops={}) {
7174
});
7275
}
7376
render() {
74-
return <ReactClass {...this.state} {...initprops} actions={this.actions} />
77+
return <ReactClass {...this.props} {...this.state} {...initprops} actions={this.actions} />
7578
}
7679
}
7780
Connect.contextTypes = CONTEXT_TYPE;

lib/test-utils.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
import * as most from 'most'
1+
import {from } from 'most'
2+
3+
function getStreamOf(component, name) {
4+
let s = component.context[name];
5+
s.take$ = n => s.take(n).forEach(_=>_)
6+
return s
7+
}
8+
29
function historyStreamOf(component) {
3-
let historyStream = component.context['__reactive.react.historyStream__'];
4-
historyStream.take$ = n => historyStream.take(n).forEach(_=>_)
5-
return historyStream
10+
return getStreamOf(component, '__reactive.react.historyStream__')
611
}
712

813
function intentStreamOf(component) {
9-
return component.context['__reactive.react.intentStream__'];
14+
return getStreamOf(component, '__reactive.react.intentStream__')
1015
}
1116

1217
function do$(listOfActions) {
13-
return most.from(listOfActions).forEach(x=>x())
18+
return from(listOfActions).forEach(x=>x())
19+
}
20+
21+
function sendTo(listOfIntent, component) {
22+
let s = intentStreamOf(component)
23+
return from(listOfIntent).forEach(i=>s.send(i))
1424
}
1525

1626
export {do$, historyStreamOf, intentStreamOf}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"scripts": {
2020
"license": "(cat LICENSE.txt; cat react-most.js) > react-most.licensed.js && mv react-most.licensed.js react-most.js",
2121
"build": "babel lib -d ./ --ignore '__tests__' && npm run license",
22-
"test": "jest",
22+
"test": "jest --coverage",
2323
"prepublish": "npm run build",
2424
"testWDebugger": "node --harmony $(which bugger) ./node_modules/jest-cli/bin/jest.js --runInBand"
2525
},

0 commit comments

Comments
 (0)