Fix CallActions.test.ts

This commit is contained in:
Jerko Steiner 2019-11-13 21:28:43 -03:00
parent 7009e53037
commit 4bdd2e4cae
8 changed files with 63 additions and 61 deletions

View File

@ -1,26 +1,38 @@
jest.mock('../socket') jest.mock('../socket')
jest.mock('../window') jest.mock('../window')
jest.mock('../store')
jest.mock('./SocketActions') jest.mock('./SocketActions')
import * as CallActions from './CallActions' import * as CallActions from './CallActions'
import * as SocketActions from './SocketActions' import * as SocketActions from './SocketActions'
import * as constants from '../constants' import * as constants from '../constants'
import socket from '../socket' import socket from '../socket'
import storeMock from '../store'
import { callId, getUserMedia } from '../window' import { callId, getUserMedia } from '../window'
import { MockStore } from 'redux-mock-store' import { bindActionCreators, createStore, AnyAction, combineReducers, applyMiddleware } from 'redux'
import { bindActionCreators } from 'redux' import reducers from '../reducers'
import { middlewares } from '../middlewares'
jest.useFakeTimers() jest.useFakeTimers()
describe('reducers/alerts', () => { describe('CallActions', () => {
const store: MockStore = storeMock as any let callActions: typeof CallActions
const callActions = bindActionCreators(CallActions, store.dispatch)
function allActions(state: AnyAction[] = [], action: AnyAction) {
return [...state, action]
}
const configureStore = () => createStore(
combineReducers({...reducers, allActions }),
applyMiddleware(...middlewares),
)
let store: ReturnType<typeof configureStore>
beforeEach(() => { beforeEach(() => {
store.clearActions(); store = createStore(
combineReducers({ allActions }),
applyMiddleware(...middlewares),
)
callActions = bindActionCreators(CallActions, store.dispatch);
(getUserMedia as any).fail(false); (getUserMedia as any).fail(false);
(SocketActions.handshake as jest.Mock).mockReturnValue(jest.fn()) (SocketActions.handshake as jest.Mock).mockReturnValue(jest.fn())
}) })
@ -35,9 +47,8 @@ describe('reducers/alerts', () => {
it('calls handshake.init when connected & got camera stream', async () => { it('calls handshake.init when connected & got camera stream', async () => {
const promise = callActions.init() const promise = callActions.init()
socket.emit('connect') socket.emit('connect')
expect(store.getActions()).toEqual([{ await promise
type: constants.INIT_PENDING, expect(store.getState().allActions.slice(1)).toEqual([{
}, {
type: constants.NOTIFY, type: constants.NOTIFY,
payload: { payload: {
id: jasmine.any(String), id: jasmine.any(String),
@ -45,15 +56,14 @@ describe('reducers/alerts', () => {
type: 'warning', type: 'warning',
}, },
}, { }, {
type: constants.MESSAGE_ADD, type: constants.STREAM_ADD,
payload: { payload: {
image: null, stream: jasmine.anything(),
message: 'Connected to server socket', userId: constants.ME,
timestamp: jasmine.any(String),
userId: '[PeerCalls]',
}, },
}, {
type: constants.INIT,
}]) }])
await promise
expect((SocketActions.handshake as jest.Mock).mock.calls).toEqual([[{ expect((SocketActions.handshake as jest.Mock).mock.calls).toEqual([[{
socket, socket,
roomName: callId, roomName: callId,
@ -65,23 +75,13 @@ describe('reducers/alerts', () => {
const promise = callActions.init() const promise = callActions.init()
socket.emit('connect') socket.emit('connect')
socket.emit('disconnect') socket.emit('disconnect')
expect(store.getActions()).toEqual([{ expect(store.getState().allActions.slice(1)).toEqual([{
type: constants.INIT_PENDING,
}, {
type: constants.NOTIFY, type: constants.NOTIFY,
payload: { payload: {
id: jasmine.any(String), id: jasmine.any(String),
message: 'Connected to server socket', message: 'Connected to server socket',
type: 'warning', type: 'warning',
}, },
}, {
type: constants.MESSAGE_ADD,
payload: {
image: null,
message: 'Connected to server socket',
timestamp: jasmine.any(String),
userId: '[PeerCalls]',
},
}, { }, {
type: constants.NOTIFY, type: constants.NOTIFY,
payload: { payload: {
@ -89,14 +89,6 @@ describe('reducers/alerts', () => {
message: 'Server socket disconnected', message: 'Server socket disconnected',
type: 'error', type: 'error',
}, },
}, {
type: constants.MESSAGE_ADD,
payload: {
image: null,
message: 'Server socket disconnected',
timestamp: jasmine.any(String),
userId: '[PeerCalls]',
},
}]) }])
await promise await promise
}) })

View File

@ -4,7 +4,8 @@ import * as StreamActions from './StreamActions'
import * as constants from '../constants' import * as constants from '../constants'
import socket from '../socket' import socket from '../socket'
import { callId, getUserMedia } from '../window' import { callId, getUserMedia } from '../window'
import { Dispatch, GetState } from '../store' import { Dispatch, GetState, ThunkResult } from '../store'
import { makeAction } from '../async'
export interface InitAction { export interface InitAction {
type: 'INIT' type: 'INIT'
@ -19,20 +20,21 @@ const initialize = (): InitializeAction => ({
type: 'INIT', type: 'INIT',
}) })
export const init = () => async (dispatch: Dispatch, getState: GetState) => { export const init = (): ThunkResult<Promise<void>> =>
dispatch(initialize()) async (dispatch, getState) => {
const socket = await dispatch(connect())
const stream = await dispatch(getCameraStream())
const socket = await connect(dispatch) dispatch(SocketActions.handshake({
const stream = await getCameraStream(dispatch)
SocketActions.handshake({
socket, socket,
roomName: callId, roomName: callId,
stream, stream,
})(dispatch, getState) }))
dispatch(initialize())
} }
export async function connect (dispatch: Dispatch) { export const connect = () => (dispatch: Dispatch) => {
return new Promise<SocketIOClient.Socket>(resolve => { return new Promise<SocketIOClient.Socket>(resolve => {
socket.once('connect', () => { socket.once('connect', () => {
resolve(socket) resolve(socket)
@ -46,7 +48,7 @@ export async function connect (dispatch: Dispatch) {
}) })
} }
export async function getCameraStream (dispatch: Dispatch) { export const getCameraStream = () => async (dispatch: Dispatch) => {
try { try {
const stream = await getUserMedia({ const stream = await getUserMedia({
video: { facingMode: 'user' }, video: { facingMode: 'user' },

View File

@ -27,15 +27,15 @@ function notify(dispatch: Dispatch, type: NotifyType, args: string[]) {
} }
export const info = (...args: any[]): ThunkResult<NotificationAddAction> => { export const info = (...args: any[]): ThunkResult<NotificationAddAction> => {
return dispatch => notify(dispatch, 'info', args) return dispatch => dispatch(notify(dispatch, 'info', args))
} }
export const warning = (...args: any[]): ThunkResult<NotificationAddAction> => { export const warning = (...args: any[]): ThunkResult<NotificationAddAction> => {
return dispatch => notify(dispatch, 'warning', args) return dispatch => dispatch(notify(dispatch, 'warning', args))
} }
export const error = (...args: any[]): ThunkResult<NotificationAddAction> => { export const error = (...args: any[]): ThunkResult<NotificationAddAction> => {
return dispatch => notify(dispatch, 'error', args) return dispatch => dispatch(notify(dispatch, 'error', args))
} }
function addNotification(payload: Notification): NotificationAddAction { function addNotification(payload: Notification): NotificationAddAction {

View File

@ -1,4 +1,4 @@
import { Action } from 'redux' import { Action, Dispatch } from 'redux'
export type PendingAction<T extends string, P> = Action<T> & Promise<P> & { export type PendingAction<T extends string, P> = Action<T> & Promise<P> & {
status: 'pending' status: 'pending'
@ -49,9 +49,9 @@ export function isPendingAction(
export function makeAction<A extends unknown[], T extends string, P>( export function makeAction<A extends unknown[], T extends string, P>(
type: T, type: T,
impl: (...args: A) => Promise<P>, impl: (...args: A) => Promise<P>,
): (...args: A) => PendingAction<T, P>{ ): (...args: A) => PendingAction<T, P> {
return (...args: A) => { return (...args: A) => {
const pendingAction= impl(...args) as PendingAction<T, P> const pendingAction = impl(...args) as PendingAction<T, P>
pendingAction.type = type pendingAction.type = type
pendingAction.status = 'pending' pendingAction.status = 'pending'
return pendingAction return pendingAction

View File

@ -1,13 +1,20 @@
import { AnyAction, Middleware } from 'redux' import { AnyAction, Middleware } from 'redux'
import { isPendingAction, ResolvedAction, PendingAction, RejectedAction } from './action' import { isPendingAction, ResolvedAction, RejectedAction } from './action'
import _debug from 'debug'
const debug = _debug('peercalls:async')
export const middleware: Middleware = store => next => (action: AnyAction) => { export const middleware: Middleware = store => next => (action: AnyAction) => {
if (!isPendingAction(action)) { if (!isPendingAction(action)) {
debug('NOT pending %o', action)
return next(action) return next(action)
} }
debug('Pending: %s %s', action.type, action.status)
const promise = action const promise = action
.then(payload => { .then(payload => {
debug('Resolved: %s resolved', action.type)
const resolvedAction: ResolvedAction<string, unknown> = { const resolvedAction: ResolvedAction<string, unknown> = {
payload, payload,
type: action.type, type: action.type,
@ -17,6 +24,7 @@ export const middleware: Middleware = store => next => (action: AnyAction) => {
}) })
// Propagate this action. Only attach listeners to the promise. // Propagate this action. Only attach listeners to the promise.
debug('Calling next for %s %s', action.type, action.status)
next({ next({
type: action.type, type: action.type,
status: 'pending', status: 'pending',
@ -24,6 +32,7 @@ export const middleware: Middleware = store => next => (action: AnyAction) => {
const promise2 = promise const promise2 = promise
.catch((err: Error) => { .catch((err: Error) => {
debug('Rejected: %s rejected %s', action.type, err.stack)
const rejectedAction: RejectedAction<string> = { const rejectedAction: RejectedAction<string> = {
payload: err, payload: err,
type: action.type, type: action.type,
@ -32,5 +41,7 @@ export const middleware: Middleware = store => next => (action: AnyAction) => {
store.dispatch(rejectedAction) store.dispatch(rejectedAction)
}) })
return promise2.then(() => action) return promise2.then(() => {
return action
})
} }

View File

@ -6,9 +6,6 @@ export const ALERT_DISMISS = 'ALERT_DISMISS'
export const ALERT_CLEAR = 'ALERT_CLEAR' export const ALERT_CLEAR = 'ALERT_CLEAR'
export const INIT = 'INIT' export const INIT = 'INIT'
export const INIT_PENDING = `${INIT}_PENDING`
export const INIT_FULFILLED = `${INIT}_FULFILLED`
export const INIT_REJECTED = `${INIT}_REJECTED`
export const ME = '_me_' export const ME = '_me_'

View File

@ -1,8 +1,9 @@
import { Middleware } from 'redux'
import logger from 'redux-logger' import logger from 'redux-logger'
import promiseMiddleware from 'redux-promise-middleware'
import thunk from 'redux-thunk' import thunk from 'redux-thunk'
import { middleware as asyncMiddleware } from './async'
export const middlewares = [thunk, promiseMiddleware()] export const middlewares: Middleware[] = [thunk, asyncMiddleware]
export const create = (log = false) => { export const create = (log = false) => {
const m = middlewares.slice() const m = middlewares.slice()
log && m.push(logger) log && m.push(logger)

View File

@ -1,7 +1,6 @@
import { Action, applyMiddleware, createStore as _createStore, Store as ReduxStore } from 'redux' import { Action, applyMiddleware, createStore as _createStore, Store as ReduxStore } from 'redux'
import { ThunkAction, ThunkDispatch } from 'redux-thunk' import { ThunkAction, ThunkDispatch } from 'redux-thunk'
import { create } from './middlewares' import { create } from './middlewares'
import { middleware as asyncMiddleware }from './async'
import reducers from './reducers' import reducers from './reducers'
export const middlewares = create( export const middlewares = create(
@ -10,7 +9,7 @@ export const middlewares = create(
export const createStore = () => _createStore( export const createStore = () => _createStore(
reducers, reducers,
applyMiddleware(...middlewares, asyncMiddleware), applyMiddleware(...middlewares),
) )
export default createStore() export default createStore()