Add tests for actions & reducers
This commit is contained in:
parent
46ec853280
commit
300afd6b2f
@ -4,7 +4,8 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"max-len": [2, 80, 4],
|
"max-len": [2, 80, 4],
|
||||||
"jsx-quotes": ["error", "prefer-double"],
|
"jsx-quotes": ["error", "prefer-double"],
|
||||||
"padded-blocks": 0
|
"padded-blocks": 0,
|
||||||
|
"import/first": 0
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"expect": true,
|
"expect": true,
|
||||||
|
|||||||
4
Makefile
4
Makefile
@ -52,7 +52,7 @@ lint-fix:
|
|||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
|
|
||||||
jest --verbose
|
jest --forceExit
|
||||||
|
|
||||||
.PHONY: testify
|
.PHONY: testify
|
||||||
testify:
|
testify:
|
||||||
@ -62,7 +62,7 @@ testify:
|
|||||||
.PHONY: coverage
|
.PHONY: coverage
|
||||||
coverage:
|
coverage:
|
||||||
|
|
||||||
jest --coverage
|
jest --coverage --forceExit
|
||||||
|
|
||||||
.PHONY: server
|
.PHONY: server
|
||||||
server:
|
server:
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
const Peer = jest.genMockFunction().mockImplementation(() => {
|
const Peer = jest.genMockFunction().mockImplementation(() => {
|
||||||
let peer = new EventEmitter()
|
let peer = new EventEmitter()
|
||||||
peer.destroy = jest.genMockFunction()
|
peer.destroy = jest.fn()
|
||||||
peer.signal = jest.genMockFunction()
|
peer.signal = jest.fn()
|
||||||
|
peer.send = jest.fn()
|
||||||
Peer.instances.push(peer)
|
Peer.instances.push(peer)
|
||||||
return peer
|
return peer
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js",
|
"start": "node src/index.js",
|
||||||
"test": "jest --coverage",
|
"test": "jest --coverage --forceExit",
|
||||||
"testify": "jest --watch",
|
"testify": "jest --watch",
|
||||||
"lint": "eslint ./index.js ./src/js"
|
"lint": "eslint ./index.js ./src/js"
|
||||||
},
|
},
|
||||||
|
|||||||
2
src/client/__mocks__/socket.js
Normal file
2
src/client/__mocks__/socket.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import EventEmitter from 'events'
|
||||||
|
export default new EventEmitter()
|
||||||
@ -6,21 +6,23 @@ import getUserMedia from '../window/getUserMedia.js'
|
|||||||
import handshake from '../peer/handshake.js'
|
import handshake from '../peer/handshake.js'
|
||||||
import socket from '../socket.js'
|
import socket from '../socket.js'
|
||||||
|
|
||||||
export const init = () => dispatch => ({
|
export const init = () => dispatch => {
|
||||||
type: constants.INIT,
|
return dispatch({
|
||||||
payload: Promise.all([
|
type: constants.INIT,
|
||||||
connect()(dispatch),
|
payload: Promise.all([
|
||||||
getCameraStream()(dispatch)
|
connect()(dispatch),
|
||||||
])
|
getCameraStream()(dispatch)
|
||||||
.spread((socket, stream) => {
|
])
|
||||||
handshake.init({ socket, callId, stream })
|
.spread((socket, stream) => {
|
||||||
|
handshake({ socket, callId, stream })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
export const connect = () => dispatch => {
|
export const connect = () => dispatch => {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
socket.once('connect', () => {
|
socket.once('connect', () => {
|
||||||
dispatch(NotifyActions.warn('Connected to server socket'))
|
dispatch(NotifyActions.warning('Connected to server socket'))
|
||||||
resolve(socket)
|
resolve(socket)
|
||||||
})
|
})
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
@ -35,9 +37,9 @@ export const getCameraStream = () => dispatch => {
|
|||||||
dispatch(addStream({ stream, userId: constants.ME }))
|
dispatch(addStream({ stream, userId: constants.ME }))
|
||||||
return stream
|
return stream
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(err => {
|
||||||
dispatch(NotifyActions.alert('Could not get access to microphone & camera'))
|
dispatch(NotifyActions.alert('Could not get access to microphone & camera'))
|
||||||
return null
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import * as constants from '../constants.js'
|
import * as constants from '../constants.js'
|
||||||
|
import Immutable from 'seamless-immutable'
|
||||||
|
|
||||||
const TIMEOUT = 5000
|
const TIMEOUT = 5000
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ function format (string, args) {
|
|||||||
const _notify = (type, args) => dispatch => {
|
const _notify = (type, args) => dispatch => {
|
||||||
let string = args[0] || ''
|
let string = args[0] || ''
|
||||||
let message = format(string, Array.prototype.slice.call(args, 1))
|
let message = format(string, Array.prototype.slice.call(args, 1))
|
||||||
const payload = { type, message }
|
const payload = Immutable({ type, message })
|
||||||
dispatch({
|
dispatch({
|
||||||
type: constants.NOTIFY,
|
type: constants.NOTIFY,
|
||||||
payload
|
payload
|
||||||
@ -24,18 +25,22 @@ const _notify = (type, args) => dispatch => {
|
|||||||
}, TIMEOUT)
|
}, TIMEOUT)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const info = function() {
|
export const info = function () {
|
||||||
return dispatch => _notify('info', arguments)(dispatch)
|
return dispatch => _notify('info', arguments)(dispatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const warn = function() {
|
export const warning = function () {
|
||||||
return dispatch => _notify('warning', arguments)(dispatch)
|
return dispatch => _notify('warning', arguments)(dispatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const error = function() {
|
export const error = function () {
|
||||||
return dispatch => _notify('error', arguments)(dispatch)
|
return dispatch => _notify('error', arguments)(dispatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const clear = () => ({
|
||||||
|
type: constants.NOTIFY_CLEAR
|
||||||
|
})
|
||||||
|
|
||||||
export function alert (message, dismissable) {
|
export function alert (message, dismissable) {
|
||||||
return {
|
return {
|
||||||
type: constants.ALERT,
|
type: constants.ALERT,
|
||||||
@ -48,9 +53,15 @@ export function alert (message, dismissable) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dismiss = alert => {
|
export const dismissAlert = alert => {
|
||||||
return {
|
return {
|
||||||
type: constants.ALERT_DISMISS,
|
type: constants.ALERT_DISMISS,
|
||||||
payload: alert
|
payload: alert
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const clearAlerts = () => {
|
||||||
|
return {
|
||||||
|
type: constants.ALERT_CLEAR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
93
src/client/actions/__tests__/CallActions-test.js
Normal file
93
src/client/actions/__tests__/CallActions-test.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
jest.mock('../../callId.js')
|
||||||
|
jest.mock('../../iceServers.js')
|
||||||
|
jest.mock('../../peer/handshake.js')
|
||||||
|
jest.mock('../../socket.js')
|
||||||
|
jest.mock('../../window/getUserMedia.js')
|
||||||
|
jest.mock('../../store.js')
|
||||||
|
|
||||||
|
import * as CallActions from '../CallActions.js'
|
||||||
|
import * as constants from '../../constants.js'
|
||||||
|
import * as getUserMediaMock from '../../window/getUserMedia.js'
|
||||||
|
import callId from '../../callId.js'
|
||||||
|
import handshake from '../../peer/handshake.js'
|
||||||
|
import socket from '../../socket.js'
|
||||||
|
import store from '../../store.js'
|
||||||
|
|
||||||
|
jest.useFakeTimers()
|
||||||
|
|
||||||
|
describe('reducers/alerts', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store.clearActions()
|
||||||
|
getUserMediaMock.fail(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.runAllTimers()
|
||||||
|
socket.removeAllListeners('connect')
|
||||||
|
socket.removeAllListeners('disconnect')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('init', () => {
|
||||||
|
|
||||||
|
it('calls handshake.init when connected & got camera stream', done => {
|
||||||
|
const promise = store.dispatch(CallActions.init())
|
||||||
|
socket.emit('connect')
|
||||||
|
expect(store.getActions()).toEqual([{
|
||||||
|
type: constants.INIT_PENDING
|
||||||
|
}, {
|
||||||
|
type: constants.NOTIFY,
|
||||||
|
payload: {
|
||||||
|
message: 'Connected to server socket',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
promise.then(() => {
|
||||||
|
expect(handshake.mock.calls).toEqual([[{
|
||||||
|
socket,
|
||||||
|
callId,
|
||||||
|
stream: getUserMediaMock.stream
|
||||||
|
}]])
|
||||||
|
})
|
||||||
|
.then(done)
|
||||||
|
.catch(done.fail)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls dispatches disconnect message on disconnect', done => {
|
||||||
|
const promise = store.dispatch(CallActions.init())
|
||||||
|
socket.emit('connect')
|
||||||
|
socket.emit('disconnect')
|
||||||
|
expect(store.getActions()).toEqual([{
|
||||||
|
type: constants.INIT_PENDING
|
||||||
|
}, {
|
||||||
|
type: constants.NOTIFY,
|
||||||
|
payload: {
|
||||||
|
message: 'Connected to server socket',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: constants.NOTIFY,
|
||||||
|
payload: {
|
||||||
|
message: 'Server socket disconnected',
|
||||||
|
type: 'error'
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
promise.then(done).catch(done.fail)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches alert when failed to get media stream', done => {
|
||||||
|
getUserMediaMock.fail(true)
|
||||||
|
const promise = store.dispatch(CallActions.init())
|
||||||
|
socket.emit('connect')
|
||||||
|
promise
|
||||||
|
.then(done.fail)
|
||||||
|
.catch(err => {
|
||||||
|
expect(err.message).toEqual('test')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ import _ from 'underscore'
|
|||||||
|
|
||||||
export default class App extends React.Component {
|
export default class App extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
dismissAlert: PropTypes.func.isRequired,
|
||||||
streams: PropTypes.objectOf(StreamPropType).isRequired,
|
streams: PropTypes.objectOf(StreamPropType).isRequired,
|
||||||
alerts: PropTypes.arrayOf(AlertPropType).isRequired,
|
alerts: PropTypes.arrayOf(AlertPropType).isRequired,
|
||||||
activate: PropTypes.func.isRequired,
|
activate: PropTypes.func.isRequired,
|
||||||
@ -21,11 +22,11 @@ export default class App extends React.Component {
|
|||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
active, activate, alerts, dismiss, notify, notifications, streams
|
active, activate, alerts, dismissAlert, notify, notifications, streams
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return (<div className="app">
|
return (<div className="app">
|
||||||
<Alerts alerts={alerts} dismiss={dismiss} />
|
<Alerts alerts={alerts} dismiss={dismissAlert} />
|
||||||
<Notifications notifications={notifications} />
|
<Notifications notifications={notifications} />
|
||||||
<Input notify={notify} />
|
<Input notify={notify} />
|
||||||
<div className="videos">
|
<div className="videos">
|
||||||
|
|||||||
@ -2,9 +2,9 @@ import { PENDING, FULFILLED, REJECTED } from 'redux-promise-middleware'
|
|||||||
export const ME = '_me_'
|
export const ME = '_me_'
|
||||||
|
|
||||||
export const INIT = 'INIT'
|
export const INIT = 'INIT'
|
||||||
export const INIT_PENDING = `${INIT}${PENDING}`
|
export const INIT_PENDING = `${INIT}_${PENDING}`
|
||||||
export const INIT_FULFILLED = `${INIT}${FULFILLED}`
|
export const INIT_FULFILLED = `${INIT}_${FULFILLED}`
|
||||||
export const INIT_REJECTED = `${INIT}${REJECTED}`
|
export const INIT_REJECTED = `${INIT}_${REJECTED}`
|
||||||
|
|
||||||
export const ALERT = 'ALERT'
|
export const ALERT = 'ALERT'
|
||||||
export const ALERT_DISMISS = 'ALERT_DISMISS'
|
export const ALERT_DISMISS = 'ALERT_DISMISS'
|
||||||
|
|||||||
@ -18,7 +18,7 @@ function mapStateToProps(state) {
|
|||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
activate: bindActionCreators(CallActions.activateStream, dispatch),
|
activate: bindActionCreators(CallActions.activateStream, dispatch),
|
||||||
dismiss: bindActionCreators(NotifyActions.dismiss, dispatch),
|
dismissAlert: bindActionCreators(NotifyActions.dismissAlert, dispatch),
|
||||||
init: bindActionCreators(CallActions.init, dispatch),
|
init: bindActionCreators(CallActions.init, dispatch),
|
||||||
notify: bindActionCreators(NotifyActions.info, dispatch)
|
notify: bindActionCreators(NotifyActions.info, dispatch)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ jest.mock('../../callId.js')
|
|||||||
jest.mock('../../iceServers.js')
|
jest.mock('../../iceServers.js')
|
||||||
|
|
||||||
import * as constants from '../../constants.js'
|
import * as constants from '../../constants.js'
|
||||||
import * as handshake from '../handshake.js'
|
import handshake from '../handshake.js'
|
||||||
import Peer from 'simple-peer'
|
import Peer from 'simple-peer'
|
||||||
import peers from '../peers.js'
|
import peers from '../peers.js'
|
||||||
import store from '../../store.js'
|
import store from '../../store.js'
|
||||||
@ -25,7 +25,7 @@ describe('handshake', () => {
|
|||||||
describe('socket events', () => {
|
describe('socket events', () => {
|
||||||
describe('users', () => {
|
describe('users', () => {
|
||||||
it('add a peer for each new user and destroy peers for missing', () => {
|
it('add a peer for each new user and destroy peers for missing', () => {
|
||||||
handshake.init(socket, 'bla')
|
handshake(socket, 'bla')
|
||||||
|
|
||||||
// given
|
// given
|
||||||
let payload = {
|
let payload = {
|
||||||
@ -53,7 +53,7 @@ describe('handshake', () => {
|
|||||||
let data
|
let data
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
data = {}
|
data = {}
|
||||||
handshake.init(socket, 'bla')
|
handshake(socket, 'bla')
|
||||||
socket.emit('users', {
|
socket.emit('users', {
|
||||||
initiator: 'a',
|
initiator: 'a',
|
||||||
users: [{ id: 'a' }, { id: 'b' }]
|
users: [{ id: 'a' }, { id: 'b' }]
|
||||||
@ -88,7 +88,7 @@ describe('handshake', () => {
|
|||||||
let ready = false
|
let ready = false
|
||||||
socket.once('ready', () => { ready = true })
|
socket.once('ready', () => { ready = true })
|
||||||
|
|
||||||
handshake.init(socket, 'bla')
|
handshake(socket, 'bla')
|
||||||
|
|
||||||
socket.emit('users', {
|
socket.emit('users', {
|
||||||
initiator: 'a',
|
initiator: 'a',
|
||||||
|
|||||||
@ -26,8 +26,6 @@ import store from '../../store.js'
|
|||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import { play } from '../../window/video.js'
|
import { play } from '../../window/video.js'
|
||||||
|
|
||||||
const { dispatch } = store
|
|
||||||
|
|
||||||
describe('peers', () => {
|
describe('peers', () => {
|
||||||
function createSocket () {
|
function createSocket () {
|
||||||
const socket = new EventEmitter()
|
const socket = new EventEmitter()
|
||||||
@ -71,7 +69,6 @@ describe('peers', () => {
|
|||||||
peers.create({ socket, user, initiator: 'user2', stream })
|
peers.create({ socket, user, initiator: 'user2', stream })
|
||||||
|
|
||||||
expect(store.getActions()).toEqual([actions.connecting])
|
expect(store.getActions()).toEqual([actions.connecting])
|
||||||
// expect(notify.warn.mock.calls).toEqual([[ 'Connecting to peer...' ]])
|
|
||||||
|
|
||||||
expect(Peer.instances.length).toBe(1)
|
expect(Peer.instances.length).toBe(1)
|
||||||
expect(Peer.mock.calls.length).toBe(1)
|
expect(Peer.mock.calls.length).toBe(1)
|
||||||
@ -121,6 +118,34 @@ describe('peers', () => {
|
|||||||
expect(play.mock.calls.length).toBe(1)
|
expect(play.mock.calls.length).toBe(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('data', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
window.TextDecoder = class TextDecoder {
|
||||||
|
constructor(encoding) {
|
||||||
|
this.encoding = encoding
|
||||||
|
}
|
||||||
|
decode(object) {
|
||||||
|
return object.toString(this.encoding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('decodes a message', () => {
|
||||||
|
store.clearActions()
|
||||||
|
const message = 'test'
|
||||||
|
const object = JSON.stringify({ message })
|
||||||
|
peer.emit('data', Buffer.from(object, 'utf-8'))
|
||||||
|
expect(store.getActions()).toEqual([{
|
||||||
|
type: constants.NOTIFY,
|
||||||
|
payload: {
|
||||||
|
type: 'info',
|
||||||
|
message: `${user.id}: ${message}`
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('get', () => {
|
describe('get', () => {
|
||||||
@ -138,10 +163,10 @@ describe('peers', () => {
|
|||||||
describe('getIds', () => {
|
describe('getIds', () => {
|
||||||
it('returns ids of all peers', () => {
|
it('returns ids of all peers', () => {
|
||||||
peers.create({
|
peers.create({
|
||||||
socket, user: {id: 'user2' }, initiator: 'user2', stream
|
socket, user: { id: 'user2' }, initiator: 'user2', stream
|
||||||
})
|
})
|
||||||
peers.create({
|
peers.create({
|
||||||
socket, user: {id: 'user3' }, initiator: 'user3', stream
|
socket, user: { id: 'user3' }, initiator: 'user3', stream
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(peers.getIds()).toEqual([ 'user2', 'user3' ])
|
expect(peers.getIds()).toEqual([ 'user2', 'user3' ])
|
||||||
@ -165,10 +190,10 @@ describe('peers', () => {
|
|||||||
describe('clear', () => {
|
describe('clear', () => {
|
||||||
it('destroys all peers and removes them', () => {
|
it('destroys all peers and removes them', () => {
|
||||||
peers.create({
|
peers.create({
|
||||||
socket, user: {id: 'user2' }, initiator: 'user2', stream
|
socket, user: { id: 'user2' }, initiator: 'user2', stream
|
||||||
})
|
})
|
||||||
peers.create({
|
peers.create({
|
||||||
socket, user: {id: 'user3' }, initiator: 'user3', stream
|
socket, user: { id: 'user3' }, initiator: 'user3', stream
|
||||||
})
|
})
|
||||||
|
|
||||||
peers.clear()
|
peers.clear()
|
||||||
@ -179,4 +204,22 @@ describe('peers', () => {
|
|||||||
expect(peers.getIds()).toEqual([])
|
expect(peers.getIds()).toEqual([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('message', () => {
|
||||||
|
|
||||||
|
it('sends a message to all peers', () => {
|
||||||
|
peers.create({
|
||||||
|
socket, user: { id: 'user2' }, initiator: 'user2', stream
|
||||||
|
})
|
||||||
|
peers.create({
|
||||||
|
socket, user: { id: 'user3' }, initiator: 'user3', stream
|
||||||
|
})
|
||||||
|
peers.message('test')
|
||||||
|
expect(peers.get('user2').send.mock.calls)
|
||||||
|
.toEqual([[ '{"message":"test"}' ]])
|
||||||
|
expect(peers.get('user3').send.mock.calls)
|
||||||
|
.toEqual([[ '{"message":"test"}' ]])
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import store from '../store.js'
|
|||||||
const debug = _debug('peercalls')
|
const debug = _debug('peercalls')
|
||||||
const { dispatch } = store
|
const { dispatch } = store
|
||||||
|
|
||||||
export function init (socket, roomName, stream) {
|
export default function init (socket, roomName, stream) {
|
||||||
function createPeer (user, initiator) {
|
function createPeer (user, initiator) {
|
||||||
return peers.create({ socket, user, initiator, stream })
|
return peers.create({ socket, user, initiator, stream })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ let peers = {}
|
|||||||
function create ({ socket, user, initiator, stream }) {
|
function create ({ socket, user, initiator, stream }) {
|
||||||
debug('create peer: %s, stream:', user.id, stream)
|
debug('create peer: %s, stream:', user.id, stream)
|
||||||
dispatch(
|
dispatch(
|
||||||
NotifyActions.warn('Connecting to peer...')
|
NotifyActions.warning('Connecting to peer...')
|
||||||
)
|
)
|
||||||
|
|
||||||
if (peers[user.id]) {
|
if (peers[user.id]) {
|
||||||
@ -56,7 +56,7 @@ function create ({ socket, user, initiator, stream }) {
|
|||||||
peer.once('connect', () => {
|
peer.once('connect', () => {
|
||||||
debug('peer: %s, connect', user.id)
|
debug('peer: %s, connect', user.id)
|
||||||
dispatch(
|
dispatch(
|
||||||
NotifyActions.warn('Peer connection established')
|
NotifyActions.warning('Peer connection established')
|
||||||
)
|
)
|
||||||
play()
|
play()
|
||||||
})
|
})
|
||||||
@ -86,9 +86,7 @@ function create ({ socket, user, initiator, stream }) {
|
|||||||
CallActions.removeStream(user.id)
|
CallActions.removeStream(user.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
// make sure some other peer with same id didn't take place between calling
|
delete peers[user.id]
|
||||||
// `destroy()` and `close` event
|
|
||||||
if (peers[user.id] === peer) delete peers[user.id]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
95
src/client/reducers/__tests__/alerts-test.js
Normal file
95
src/client/reducers/__tests__/alerts-test.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import * as NotifyActions from '../../actions/NotifyActions.js'
|
||||||
|
import { applyMiddleware, createStore } from 'redux'
|
||||||
|
import { create } from '../../middlewares.js'
|
||||||
|
import reducers from '../index.js'
|
||||||
|
|
||||||
|
jest.useFakeTimers()
|
||||||
|
|
||||||
|
describe('reducers/alerts', () => {
|
||||||
|
|
||||||
|
let store
|
||||||
|
beforeEach(() => {
|
||||||
|
store = createStore(
|
||||||
|
reducers,
|
||||||
|
applyMiddleware.apply(null, create())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('clearAlert', () => {
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
true: 'Dismiss',
|
||||||
|
false: ''
|
||||||
|
}
|
||||||
|
;[true, false].forEach(dismissable => {
|
||||||
|
beforeEach(() => {
|
||||||
|
store.dispatch(NotifyActions.clearAlerts())
|
||||||
|
})
|
||||||
|
it('adds alert to store', () => {
|
||||||
|
store.dispatch(NotifyActions.alert('test', dismissable))
|
||||||
|
expect(store.getState().alerts).toEqual([{
|
||||||
|
action: actions[dismissable],
|
||||||
|
dismissable,
|
||||||
|
message: 'test',
|
||||||
|
type: 'warning'
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('dismissAlert', () => {
|
||||||
|
|
||||||
|
it('removes an alert', () => {
|
||||||
|
store.dispatch(NotifyActions.alert('test', true))
|
||||||
|
expect(store.getState().alerts.length).toBe(1)
|
||||||
|
store.dispatch(NotifyActions.dismissAlert(store.getState().alerts[0]))
|
||||||
|
expect(store.getState().alerts.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not remove an alert when not found', () => {
|
||||||
|
store.dispatch(NotifyActions.alert('test', true))
|
||||||
|
expect(store.getState().alerts.length).toBe(1)
|
||||||
|
store.dispatch(NotifyActions.dismissAlert({}))
|
||||||
|
expect(store.getState().alerts.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
;['info', 'warning', 'error'].forEach(type => {
|
||||||
|
|
||||||
|
describe(type, () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store.dispatch(NotifyActions[type]('Hi {0}!', 'John'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds a notification', () => {
|
||||||
|
expect(store.getState().notifications).toEqual([{
|
||||||
|
message: 'Hi John!',
|
||||||
|
type
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dismisses notification after a timeout', () => {
|
||||||
|
jest.runAllTimers()
|
||||||
|
expect(store.getState().notifications).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('clear', () => {
|
||||||
|
|
||||||
|
it('clears all alerts', () => {
|
||||||
|
store.dispatch(NotifyActions.info('Hi {0}!', 'John'))
|
||||||
|
store.dispatch(NotifyActions.warning('Hi {0}!', 'John'))
|
||||||
|
store.dispatch(NotifyActions.error('Hi {0}!', 'John'))
|
||||||
|
store.dispatch(NotifyActions.clear())
|
||||||
|
expect(store.getState().notifications).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
70
src/client/reducers/__tests__/streams-test.js
Normal file
70
src/client/reducers/__tests__/streams-test.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
jest.mock('../../callId.js')
|
||||||
|
jest.mock('../../iceServers.js')
|
||||||
|
jest.mock('../../window/createObjectURL.js')
|
||||||
|
|
||||||
|
import * as CallActions from '../../actions/CallActions.js'
|
||||||
|
import { applyMiddleware, createStore } from 'redux'
|
||||||
|
import { create } from '../../middlewares.js'
|
||||||
|
import reducers from '../index.js'
|
||||||
|
|
||||||
|
describe('reducers/alerts', () => {
|
||||||
|
|
||||||
|
class MediaStream {}
|
||||||
|
let store, stream, userId
|
||||||
|
beforeEach(() => {
|
||||||
|
store = createStore(
|
||||||
|
reducers,
|
||||||
|
applyMiddleware.apply(null, create())
|
||||||
|
)
|
||||||
|
userId = 'test id'
|
||||||
|
stream = new MediaStream()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('defaultState', () => {
|
||||||
|
it('should have default state set', () => {
|
||||||
|
expect(store.getState().streams).toEqual({
|
||||||
|
active: null,
|
||||||
|
all: {}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('addStream', () => {
|
||||||
|
it('adds a stream', () => {
|
||||||
|
store.dispatch(CallActions.addStream({ userId, stream }))
|
||||||
|
expect(store.getState().streams).toEqual({
|
||||||
|
active: userId,
|
||||||
|
all: {
|
||||||
|
[userId]: {
|
||||||
|
userId,
|
||||||
|
stream,
|
||||||
|
url: jasmine.any(String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('removeStream', () => {
|
||||||
|
it('removes a stream', () => {
|
||||||
|
store.dispatch(CallActions.addStream({ userId, stream }))
|
||||||
|
store.dispatch(CallActions.removeStream(userId))
|
||||||
|
expect(store.getState().streams).toEqual({
|
||||||
|
active: userId,
|
||||||
|
all: {}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('activateStream', () => {
|
||||||
|
it('activates a stream', () => {
|
||||||
|
store.dispatch(CallActions.activateStream(userId))
|
||||||
|
expect(store.getState().streams).toEqual({
|
||||||
|
active: userId,
|
||||||
|
all: {}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
@ -6,7 +6,9 @@ const defaultState = Immutable([])
|
|||||||
export default function alerts (state = defaultState, action) {
|
export default function alerts (state = defaultState, action) {
|
||||||
switch (action && action.type) {
|
switch (action && action.type) {
|
||||||
case constants.ALERT:
|
case constants.ALERT:
|
||||||
return Immutable(state.asMutable().push(action.payload))
|
const alerts = state.asMutable()
|
||||||
|
alerts.push(action.payload)
|
||||||
|
return Immutable(alerts)
|
||||||
case constants.ALERT_DISMISS:
|
case constants.ALERT_DISMISS:
|
||||||
return state.filter(a => a !== action.payload)
|
return state.filter(a => a !== action.payload)
|
||||||
case constants.ALERT_CLEAR:
|
case constants.ALERT_CLEAR:
|
||||||
|
|||||||
@ -9,19 +9,19 @@ const defaultState = Immutable({
|
|||||||
|
|
||||||
function addStream (state, action) {
|
function addStream (state, action) {
|
||||||
const { userId, stream } = action.payload
|
const { userId, stream } = action.payload
|
||||||
const streams = state.all.merge({
|
const all = state.all.merge({
|
||||||
[userId]: {
|
[userId]: {
|
||||||
userId,
|
userId,
|
||||||
stream,
|
stream,
|
||||||
url: createObjectURL(stream)
|
url: createObjectURL(stream)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return { active: userId, streams }
|
return state.merge({ active: userId, all })
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeStream (state, action) {
|
function removeStream (state, action) {
|
||||||
const streams = state.all.without(action.payload.userId)
|
const all = state.all.without(action.payload.userId)
|
||||||
return state.merge({ streams })
|
return state.merge({ all })
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function streams (state = defaultState, action) {
|
export default function streams (state = defaultState, action) {
|
||||||
|
|||||||
1
src/client/window/__mocks__/createObjectURL.js
Normal file
1
src/client/window/__mocks__/createObjectURL.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default object => 'blob://' + String(object)
|
||||||
13
src/client/window/__mocks__/getUserMedia.js
Normal file
13
src/client/window/__mocks__/getUserMedia.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import Promise from 'bluebird'
|
||||||
|
|
||||||
|
class MediaStream {}
|
||||||
|
|
||||||
|
let shouldFail
|
||||||
|
export const fail = _fail => shouldFail = !!_fail
|
||||||
|
export const stream = new MediaStream()
|
||||||
|
export default function getUserMedia () {
|
||||||
|
return !shouldFail
|
||||||
|
? Promise.resolve(stream)
|
||||||
|
: Promise.reject(new Error('test'))
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user