Increase test coverage

This commit is contained in:
Jerko Steiner 2017-06-17 10:43:01 -04:00
parent 300afd6b2f
commit 06821e1fce
19 changed files with 137 additions and 52 deletions

View File

@ -2,6 +2,7 @@ jest.mock('../actions/CallActions.js')
jest.mock('../callId.js')
jest.mock('../iceServers.js')
import * as constants from '../constants.js'
import App from '../containers/App.js'
import React from 'react'
import ReactDOM from 'react-dom'
@ -14,9 +15,11 @@ import { middlewares } from '../store.js'
describe('App', () => {
const initAction = { type: 'INIT' }
let state
beforeEach(() => {
init.mockReturnValue({ type: 'INIT' })
init.mockReturnValue(initAction)
state = reducers()
})
@ -39,4 +42,56 @@ describe('App', () => {
})
})
describe('state', () => {
let alert
beforeEach(() => {
state.streams = state.streams.setIn(['all'], {
'test': {
userId: 'test',
url: 'blob://'
}
})
state.notifications = state.notifications.merge({
'notification1': {
id: 'notification1',
message: 'test',
type: 'warning'
}
})
const alerts = state.alerts.asMutable()
alert = {
dismissable: true,
action: 'Dismiss',
message: 'test alert'
}
alerts.push(alert)
state.alerts = alerts
render()
store.clearActions()
})
describe('alerts', () => {
it('can be dismissed', () => {
const dismiss = node.querySelector('.action-alert-dismiss')
TestUtils.Simulate.click(dismiss)
expect(store.getActions()).toEqual([{
type: constants.ALERT_DISMISS,
payload: alert
}])
})
})
describe('video', () => {
it('can be activated', () => {
const video = node.querySelector('video')
TestUtils.Simulate.click(video)
expect(store.getActions()).toEqual([{
type: constants.STREAM_ACTIVATE,
payload: { userId: 'test' }
}])
})
})
})
})

View File

@ -0,0 +1,11 @@
window.localStorage = { debug: true }
import logger from 'redux-logger'
const store = require('../store.js')
describe('store', () => {
it('should load logger middleware', () => {
expect(store.middlewares.some(m => m === logger)).toBeTruthy()
})
})

View File

@ -1,3 +1,4 @@
import * as StreamActions from './StreamActions.js'
import * as NotifyActions from './NotifyActions.js'
import * as constants from '../constants.js'
import Promise from 'bluebird'
@ -34,7 +35,7 @@ export const connect = () => dispatch => {
export const getCameraStream = () => dispatch => {
return getUserMedia({ video: true, audio: true })
.then(stream => {
dispatch(addStream({ stream, userId: constants.ME }))
dispatch(StreamActions.addStream({ stream, userId: constants.ME }))
return stream
})
.catch(err => {
@ -42,21 +43,3 @@ export const getCameraStream = () => dispatch => {
throw err
})
}
export const addStream = ({ stream, userId }) => ({
type: constants.STREAM_ADD,
payload: {
userId,
stream
}
})
export const removeStream = userId => ({
type: constants.STREAM_REMOVE,
payload: { userId }
})
export const activateStream = userId => ({
type: constants.STREAM_ACTIVATE,
payload: { userId }
})

View File

@ -1,5 +1,5 @@
import * as constants from '../constants.js'
import Immutable from 'seamless-immutable'
import _ from 'underscore'
const TIMEOUT = 5000
@ -12,7 +12,8 @@ function format (string, args) {
const _notify = (type, args) => dispatch => {
let string = args[0] || ''
let message = format(string, Array.prototype.slice.call(args, 1))
const payload = Immutable({ type, message })
const id = _.uniqueId('notification')
const payload = { id, type, message }
dispatch({
type: constants.NOTIFY,
payload
@ -20,7 +21,7 @@ const _notify = (type, args) => dispatch => {
setTimeout(() => {
dispatch({
type: constants.NOTIFY_DISMISS,
payload
payload: { id }
})
}, TIMEOUT)
}

View File

@ -0,0 +1,19 @@
import * as constants from '../constants.js'
export const addStream = ({ stream, userId }) => ({
type: constants.STREAM_ADD,
payload: {
userId,
stream
}
})
export const removeStream = userId => ({
type: constants.STREAM_REMOVE,
payload: { userId }
})
export const activateStream = userId => ({
type: constants.STREAM_ACTIVATE,
payload: { userId }
})

View File

@ -38,6 +38,7 @@ describe('reducers/alerts', () => {
}, {
type: constants.NOTIFY,
payload: {
id: jasmine.any(String),
message: 'Connected to server socket',
type: 'warning'
}
@ -62,12 +63,14 @@ describe('reducers/alerts', () => {
}, {
type: constants.NOTIFY,
payload: {
id: jasmine.any(String),
message: 'Connected to server socket',
type: 'warning'
}
}, {
type: constants.NOTIFY,
payload: {
id: jasmine.any(String),
message: 'Server socket disconnected',
type: 'error'
}

View File

@ -18,13 +18,16 @@ export class Alert extends React.Component {
dismiss(alert)
}
render () {
const { alert, dismiss } = this.props
const { alert } = this.props
return (
<div className={classnames('alert', alert.type)}>
<span>{alert.message}</span>
{alert.dismissable && (
<button onClick={dismiss}>{alert.action}</button>
<button
className="action-alert-dismiss"
onClick={this.dismiss}
>{alert.action}</button>
)}
</div>
)

View File

@ -34,6 +34,7 @@ export default class App extends React.Component {
<Video
activate={activate}
active={userId === active}
key={userId}
stream={stream}
/>
))}

View File

@ -11,7 +11,7 @@ export const NotificationPropTypes = PropTypes.shape({
export default class Notifications extends React.Component {
static propTypes = {
notifications: PropTypes.arrayOf(NotificationPropTypes).isRequired,
notifications: PropTypes.objectOf(NotificationPropTypes).isRequired,
max: PropTypes.number.isRequired
}
static defaultProps = {
@ -26,12 +26,12 @@ export default class Notifications extends React.Component {
transitionLeaveTimeout={100}
transitionName="fade"
>
{notifications.slice(max).map(notification => (
{Object.keys(notifications).slice(-max).map(id => (
<div
className={classnames(notification.type, 'notification')}
key={notification.id}
className={classnames(notifications[id].type, 'notification')}
key={id}
>
{notification.message}
{notifications[id].message}
</div>
))}
</CSSTransitionGroup>

View File

@ -5,14 +5,13 @@ import { ME } from '../constants.js'
export const StreamPropType = PropTypes.shape({
userId: PropTypes.string.isRequired,
stream: PropTypes.instanceOf(ArrayBuffer).isRequired,
url: PropTypes.string.isRequired
})
export default class Video extends React.Component {
static propTypes = {
activate: PropTypes.func.isRequired,
active: PropTypes.string.required,
active: PropTypes.bool.isRequired,
stream: StreamPropType.isRequired
}
activate = e => {

View File

@ -1,5 +1,6 @@
import * as NotifyActions from '../actions/NotifyActions.js'
import * as CallActions from '../actions/CallActions.js'
import * as NotifyActions from '../actions/NotifyActions.js'
import * as StreamActions from '../actions/StreamActions.js'
import App from '../components/App.js'
import React from 'react'
import { bindActionCreators } from 'redux'
@ -17,7 +18,7 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return {
activate: bindActionCreators(CallActions.activateStream, dispatch),
activate: bindActionCreators(StreamActions.activateStream, dispatch),
dismissAlert: bindActionCreators(NotifyActions.dismissAlert, dispatch),
init: bindActionCreators(CallActions.init, dispatch),
notify: bindActionCreators(NotifyActions.info, dispatch)

View File

@ -145,6 +145,7 @@ describe('handshake', () => {
expect(store.getActions()).toEqual([{
type: constants.NOTIFY,
payload: {
id: jasmine.any(String),
message: 'Peer connection closed',
type: 'error'
}

View File

@ -49,6 +49,7 @@ describe('peers', () => {
connecting: {
type: constants.NOTIFY,
payload: {
id: jasmine.any(String),
message: 'Connecting to peer...',
type: 'warning'
}
@ -56,6 +57,7 @@ describe('peers', () => {
established: {
type: constants.NOTIFY,
payload: {
id: jasmine.any(String),
message: 'Peer connection established',
type: 'warning'
}
@ -140,6 +142,7 @@ describe('peers', () => {
expect(store.getActions()).toEqual([{
type: constants.NOTIFY,
payload: {
id: jasmine.any(String),
type: 'info',
message: `${user.id}: ${message}`
}

View File

@ -1,5 +1,6 @@
import * as CallActions from '../actions/CallActions.js'
import * as NotifyActions from '../actions/NotifyActions.js'
import * as StreamActions from '../actions/StreamActions.js'
import Peer from 'simple-peer'
import _ from 'underscore'
import _debug from 'debug'
@ -63,7 +64,7 @@ function create ({ socket, user, initiator, stream }) {
peer.on('stream', stream => {
debug('peer: %s, stream', user.id)
dispatch(CallActions.addStream({
dispatch(StreamActions.addStream({
userId: user.id,
stream
}))
@ -83,7 +84,7 @@ function create ({ socket, user, initiator, stream }) {
NotifyActions.error('Peer connection closed')
)
dispatch(
CallActions.removeStream(user.id)
StreamActions.removeStream(user.id)
)
delete peers[user.id]

View File

@ -1,4 +1,5 @@
import * as NotifyActions from '../../actions/NotifyActions.js'
import _ from 'underscore'
import { applyMiddleware, createStore } from 'redux'
import { create } from '../../middlewares.js'
import reducers from '../index.js'
@ -65,7 +66,8 @@ describe('reducers/alerts', () => {
})
it('adds a notification', () => {
expect(store.getState().notifications).toEqual([{
expect(_.values(store.getState().notifications)).toEqual([{
id: jasmine.any(String),
message: 'Hi John!',
type
}])
@ -73,9 +75,14 @@ describe('reducers/alerts', () => {
it('dismisses notification after a timeout', () => {
jest.runAllTimers()
expect(store.getState().notifications).toEqual([])
expect(store.getState().notifications).toEqual({})
})
it('does not fail when no arguments', () => {
store.dispatch(NotifyActions[type]())
})
})
})
@ -87,7 +94,7 @@ describe('reducers/alerts', () => {
store.dispatch(NotifyActions.warning('Hi {0}!', 'John'))
store.dispatch(NotifyActions.error('Hi {0}!', 'John'))
store.dispatch(NotifyActions.clear())
expect(store.getState().notifications).toEqual([])
expect(store.getState().notifications).toEqual({})
})
})

View File

@ -2,7 +2,7 @@ jest.mock('../../callId.js')
jest.mock('../../iceServers.js')
jest.mock('../../window/createObjectURL.js')
import * as CallActions from '../../actions/CallActions.js'
import * as StreamActions from '../../actions/StreamActions.js'
import { applyMiddleware, createStore } from 'redux'
import { create } from '../../middlewares.js'
import reducers from '../index.js'
@ -31,13 +31,12 @@ describe('reducers/alerts', () => {
describe('addStream', () => {
it('adds a stream', () => {
store.dispatch(CallActions.addStream({ userId, stream }))
store.dispatch(StreamActions.addStream({ userId, stream }))
expect(store.getState().streams).toEqual({
active: userId,
all: {
[userId]: {
userId,
stream,
url: jasmine.any(String)
}
}
@ -47,8 +46,8 @@ describe('reducers/alerts', () => {
describe('removeStream', () => {
it('removes a stream', () => {
store.dispatch(CallActions.addStream({ userId, stream }))
store.dispatch(CallActions.removeStream(userId))
store.dispatch(StreamActions.addStream({ userId, stream }))
store.dispatch(StreamActions.removeStream(userId))
expect(store.getState().streams).toEqual({
active: userId,
all: {}
@ -58,7 +57,7 @@ describe('reducers/alerts', () => {
describe('activateStream', () => {
it('activates a stream', () => {
store.dispatch(CallActions.activateStream(userId))
store.dispatch(StreamActions.activateStream(userId))
expect(store.getState().streams).toEqual({
active: userId,
all: {}

View File

@ -1,16 +1,16 @@
import * as constants from '../constants.js'
import Immutable from 'seamless-immutable'
const defaultState = Immutable([])
const defaultState = Immutable({})
export default function notifications (state = defaultState, action) {
switch (action && action.type) {
case constants.NOTIFY:
const notifications = state.asMutable()
notifications.push(action.payload)
return Immutable(notifications)
return state.merge({
[action.payload.id]: action.payload
})
case constants.NOTIFY_DISMISS:
return state.filter(n => n !== action.payload)
return state.without(action.payload.id)
case constants.NOTIFY_CLEAR:
return defaultState
default:

View File

@ -12,7 +12,6 @@ function addStream (state, action) {
const all = state.all.merge({
[userId]: {
userId,
stream,
url: createObjectURL(stream)
}
})

View File

@ -1,7 +1,6 @@
import { create } from './middlewares.js'
import reducers from './reducers'
import { applyMiddleware, createStore } from 'redux'
export const middlewares = create(
window.localStorage && window.localStorage.debug
)