Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc97ee82

Browse files
committed
Demonstrates partial actions
1 parentd9110fb commitc97ee82

File tree

5 files changed

+86
-129
lines changed

5 files changed

+86
-129
lines changed

‎src/actions/__tests__/index_spec.ts‎

Lines changed: 52 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,37 @@ import * as actions from '../index'
66

77
constapi:jest.Mocked<apiExports.Api>=apiExports.apiasany
88

9+
consteventually=(assertFn)=>
10+
newPromise((resolve,reject)=>{
11+
setTimeout(()=>{
12+
try{
13+
assertFn()
14+
}catch(e){
15+
returnreject(e)
16+
}
17+
resolve()
18+
},1)
19+
})
20+
21+
constexpectRequest=(type,request,apiAction)=>{
22+
expect(apiAction.type).toEqual(type)
23+
expect(apiAction.request).toEqual(request)
24+
expect(apiAction.error).toBeUndefined()
25+
expect(apiAction.response).toBeUndefined()
26+
}
27+
28+
constexpectResponse=(type,response,apiAction)=>{
29+
expect(apiAction.type).toEqual(type)
30+
expect(apiAction.response).toEqual(response)
31+
expect(apiAction.response).not.toBeUndefined()
32+
}
33+
34+
constexpectError=(type,error,apiAction)=>{
35+
expect(apiAction.type).toEqual(type)
36+
expect(apiAction.response).toBeUndefined()
37+
expect(apiAction.error).toEqual(error)
38+
}
39+
940
describe('actions',()=>{
1041
conststore=()=>{
1142
constreducer=jest.fn()
@@ -14,25 +45,9 @@ describe('actions', () => {
1445
return{ dispatch, reducer}
1546
}
1647

17-
consteventually=(assertFn)=>
18-
newPromise((resolve,reject)=>{
19-
setTimeout(()=>{
20-
try{
21-
assertFn()
22-
}catch(e){
23-
returnreject(e)
24-
}
25-
resolve()
26-
},1)
27-
})
28-
29-
constexpectTypes=(reducer,types)=>
30-
()=>
31-
expect(reducer.mock.calls.map(x=>x[1].type)).toEqual(types)
32-
3348
describe('.saveCount',()=>{
3449
beforeEach(()=>{
35-
api.save.mockReturnValue(Promise.resolve(null))
50+
api.save.mockReturnValue(Promise.resolve({}))
3651
})
3752

3853
it('sends an API request',()=>{
@@ -41,13 +56,14 @@ describe('actions', () => {
4156
})
4257

4358
describe('when API request succeeds',()=>{
44-
it('dispatches SAVE_COUNT_SUCCESS',()=>{
59+
it('fills out SAVE_COUNT',()=>{
4560
const{ dispatch, reducer}=store()
4661
actions.saveCount({value:14})(dispatch)
47-
returneventually(expectTypes(reducer,[
48-
'SAVE_COUNT_REQUEST',
49-
'SAVE_COUNT_SUCCESS',
50-
]))
62+
returneventually(()=>{
63+
constactions=reducer.mock.calls.map(x=>x[1])
64+
expectRequest('SAVE_COUNT',{value:14},actions[0])
65+
expectResponse('SAVE_COUNT',{},actions[1])
66+
})
5167
})
5268
})
5369

@@ -59,26 +75,11 @@ describe('actions', () => {
5975
it('dispatches SAVE_COUNT_ERROR',()=>{
6076
const{ dispatch, reducer}=store()
6177
actions.saveCount({value:14})(dispatch)
62-
returneventually(expectTypes(reducer,[
63-
'SAVE_COUNT_REQUEST',
64-
'SAVE_COUNT_ERROR',
65-
]))
66-
})
6778

68-
it('includes error message with SAVE_COUNT_ERROR',()=>{
69-
const{ dispatch, reducer}=store()
70-
actions.saveCount({value:14})(dispatch)
7179
returneventually(()=>{
72-
expect(reducer.mock.calls[1][1].error.message)
73-
.toEqual('something terrible happened')
74-
})
75-
})
76-
77-
it('includes request with SAVE_COUNT_ERROR for convenience',()=>{
78-
const{ dispatch, reducer}=store()
79-
actions.saveCount({value:14})(dispatch)
80-
returneventually(()=>{
81-
expect(reducer.mock.calls[1][1].request).toEqual({value:14})
80+
constactions=reducer.mock.calls.map(x=>x[1])
81+
expectRequest('SAVE_COUNT',{value:14},actions[0])
82+
expectError('SAVE_COUNT','Error: something terrible happened',actions[1])
8283
})
8384
})
8485
})
@@ -95,13 +96,15 @@ describe('actions', () => {
9596
})
9697

9798
describe('when API request succeeds',()=>{
98-
it('dispatches LOAD_COUNT_SUCCESS',()=>{
99+
it('fills out LOAD_COUNT .response',()=>{
99100
const{ dispatch, reducer}=store()
100101
actions.loadCount()(dispatch)
101-
returneventually(expectTypes(reducer,[
102-
'LOAD_COUNT_REQUEST',
103-
'LOAD_COUNT_SUCCESS',
104-
]))
102+
103+
returneventually(()=>{
104+
constactions=reducer.mock.calls.map(x=>x[1])
105+
expectRequest('LOAD_COUNT',undefined,actions[0])
106+
expectResponse('LOAD_COUNT',{value:14},actions[1])
107+
})
105108
})
106109

107110
it('includes new value with LOAD_COUNT_SUCCESS',()=>{
@@ -118,21 +121,14 @@ describe('actions', () => {
118121
api.load.mockReturnValue(Promise.reject(newError('something terrible happened')))
119122
})
120123

121-
it('dispatches LOAD_COUNT_ERROR',()=>{
124+
it('fills out LOAD_COUNT .error',()=>{
122125
const{ dispatch, reducer}=store()
123126
actions.loadCount()(dispatch)
124-
returneventually(expectTypes(reducer,[
125-
'LOAD_COUNT_REQUEST',
126-
'LOAD_COUNT_ERROR',
127-
]))
128-
})
129127

130-
it('includes error message with LOAD_COUNT_ERROR',()=>{
131-
const{ dispatch, reducer}=store()
132-
actions.loadCount()(dispatch)
133128
returneventually(()=>{
134-
expect(reducer.mock.calls[1][1].error.message)
135-
.toEqual('something terrible happened')
129+
constactions=reducer.mock.calls.map(x=>x[1])
130+
expectRequest('LOAD_COUNT',undefined,actions[0])
131+
expectError('LOAD_COUNT','Error: something terrible happened',actions[1])
136132
})
137133
})
138134
})

‎src/actions/index.ts‎

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,20 @@ import * as redux from 'redux'
33
import{api}from'../api'
44
import*asstatefrom'../reducers/index'
55

6-
typeQ<T>={request:T}
7-
typeS<T>={response:T}
8-
typeE={error:Error}
9-
10-
typeQEmpty=Q<null>
11-
typeQValue=Q<{value:number}>
6+
typeAPIAction<Q,S>={
7+
request?:Q
8+
response?:S
9+
error?:string
10+
}
1211

1312
exporttypeAction=
1413
// UI actions
1514
{type:'INCREMENT_COUNTER',delta:number}
1615
|{type:'RESET_COUNTER'}
1716

1817
// API Requests
19-
|({type:'SAVE_COUNT_REQUEST'}&QValue)
20-
|({type:'SAVE_COUNT_SUCCESS'}&QValue&S<{}>)
21-
|({type:'SAVE_COUNT_ERROR'}&QValue&E)
22-
23-
|({type:'LOAD_COUNT_REQUEST'}&QEmpty)
24-
|({type:'LOAD_COUNT_SUCCESS'}&QEmpty&S<{value:number}>)
25-
|({type:'LOAD_COUNT_ERROR'}&QEmpty&E)
18+
|({type:'SAVE_COUNT'}&APIAction<{value:number},{}>)
19+
|({type:'LOAD_COUNT'}&APIAction<undefined,{value:number}>)
2620

2721
exportconstincrementCounter=(delta:number):Action=>({
2822
type:'INCREMENT_COUNTER',
@@ -33,40 +27,16 @@ export const resetCounter = (): Action => ({
3327
type:'RESET_COUNTER',
3428
})
3529

36-
exporttypeApiActionGroup<_Q,_S>={
37-
request:(q?:_Q)=>Action&Q<_Q>
38-
success:(s:_S,q?:_Q)=>Action&Q<_Q>&S<_S>
39-
error:(e:Error,q?:_Q)=>Action&Q<_Q>&E
40-
}
41-
42-
const_saveCount:ApiActionGroup<{value:number},{}>={
43-
request:(request)=>
44-
({type:'SAVE_COUNT_REQUEST', request}),
45-
success:(response,request)=>
46-
({type:'SAVE_COUNT_SUCCESS', request, response}),
47-
error:(error,request)=>
48-
({type:'SAVE_COUNT_ERROR', request, error}),
49-
}
50-
51-
const_loadCount:ApiActionGroup<null,{value:number}>={
52-
request:(request)=>
53-
({type:'LOAD_COUNT_REQUEST',request:null}),
54-
success:(response,request)=>
55-
({type:'LOAD_COUNT_SUCCESS',request:null, response}),
56-
error:(error,request)=>
57-
({type:'LOAD_COUNT_ERROR',request:null, error}),
58-
}
59-
6030
typeapiFunc<Q,S>=(q:Q)=>Promise<S>
6131

62-
functionapiActionGroupFactory<Q,S>(x:ApiActionGroup<Q,S>,go:apiFunc<Q,S>){
63-
return(request:Q)=>(dispatch:redux.Dispatch<state.All>)=>{
64-
dispatch(x.request(request))
32+
functionapiActionCreator<Q,S>(a:Action&APIAction<Q,S>,go:apiFunc<Q,S>){
33+
return(request?:Q)=>(dispatch:redux.Dispatch<state.All>)=>{
34+
dispatch({ ...a,request})
6535
go(request)
66-
.then((response)=>dispatch(x.success(response,request)))
67-
.catch((e:Error)=>dispatch(x.error(e,request)))
36+
.then((response)=>dispatch({ ...a, request, response}))
37+
.catch((e:Error)=>dispatch({ ...a, request,error:e.toString()}))
6838
}
6939
}
7040

71-
exportconstsaveCount=apiActionGroupFactory(_saveCount,api.save)
72-
exportconstloadCount=()=>apiActionGroupFactory(_loadCount,api.load)(null)
41+
exportconstsaveCount=apiActionCreator({type:'SAVE_COUNT'},api.save)
42+
exportconstloadCount=apiActionCreator({type:'LOAD_COUNT'},api.load)

‎src/api.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ const flakify = <T>(f: () => T): Promise<T> =>
1919
)
2020

2121
exporttypeApi={
22-
save(x:{value:number}):Promise<null>,
22+
save(x:{value:number}):Promise<{}>,
2323
load():(Promise<{value:number}>),
2424
}
2525

2626
exportconstapi:Api={
27-
save:(counter:{value:number}):Promise<null>=>flakify(()=>{
27+
save:counter=>flakify(()=>{
2828
localStorage.setItem('__counterValue',counter.value.toString())
29-
returnnull
29+
return{}
3030
}),
31-
load:():Promise<{value:number}>=>flakify(()=>{
31+
load:()=>flakify(()=>{
3232
conststoredValue=parseInt(localStorage.getItem('__counterValue'),10)
3333
return{
3434
value:storedValue||0,

‎src/reducers/__tests__/index_spec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('reducers/counter', () => {
3030
done()
3131
})
3232
store.dispatch({
33-
type:'LOAD_COUNT_SUCCESS',
33+
type:'LOAD_COUNT',
3434
request:{},
3535
response:{value:14}})
3636
})

‎src/reducers/index.ts‎

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,24 @@ export type All = {
1212
}
1313

1414
functionisSaving(state:boolean=false,action:Action):boolean{
15-
switch(action.type){
16-
case'SAVE_COUNT_REQUEST':
17-
returntrue
18-
case'SAVE_COUNT_SUCCESS':
19-
case'SAVE_COUNT_ERROR':
20-
returnfalse
21-
default:
22-
returnstate
15+
if(action.type==='SAVE_COUNT'){
16+
return!action.response&&!action.error
2317
}
18+
returnstate
2419
}
2520

2621
functionisLoading(state:boolean=false,action:Action):boolean{
27-
switch(action.type){
28-
case'LOAD_COUNT_REQUEST':
29-
returntrue
30-
case'LOAD_COUNT_SUCCESS':
31-
case'LOAD_COUNT_ERROR':
32-
returnfalse
33-
default:
34-
returnstate
22+
if(action.type==='LOAD_COUNT'){
23+
return!action.response&&!action.error
3524
}
25+
returnstate
3626
}
3727

3828
functionerror(state:string='',action:Action):string{
3929
switch(action.type){
40-
case'LOAD_COUNT_REQUEST':
41-
case'SAVE_COUNT_REQUEST':
42-
return''
43-
case'LOAD_COUNT_ERROR':
44-
case'SAVE_COUNT_ERROR':
45-
returnaction.error.toString()
30+
case'LOAD_COUNT':
31+
case'SAVE_COUNT':
32+
returnaction.error||''
4633
default:
4734
returnstate
4835
}
@@ -61,8 +48,12 @@ function counter (state: Counter = initialState, action: Action): Counter {
6148
case'RESET_COUNTER':
6249
return{value:0}
6350

64-
case'LOAD_COUNT_SUCCESS':
65-
return{value:action.response.value}
51+
case'LOAD_COUNT':{
52+
const{ response}=action
53+
if(response){
54+
return{value:response.value}
55+
}
56+
}
6657

6758
default:
6859
returnstate

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp