Fix broken ts build

This commit is contained in:
Jerko Steiner 2019-11-13 20:08:10 -03:00
parent 4fa6a0d17a
commit 3a5b07c218
14 changed files with 149 additions and 112 deletions

View File

@ -1,6 +1,7 @@
/* eslint-disable */
import EventEmitter from 'events'
const Peer = jest.fn().mockImplementation(() => {
let peer = new EventEmitter()
const peer = new EventEmitter()
peer.destroy = jest.fn()
peer.signal = jest.fn()
peer.send = jest.fn()

View File

@ -10,12 +10,14 @@ import socket from '../socket'
import storeMock from '../store'
import { callId, getUserMedia } from '../window'
import { MockStore } from 'redux-mock-store'
import { bindActionCreators } from 'redux'
jest.useFakeTimers()
describe('reducers/alerts', () => {
const store: MockStore = storeMock as any
const callActions = bindActionCreators(CallActions, store.dispatch)
beforeEach(() => {
store.clearActions();
@ -31,7 +33,7 @@ describe('reducers/alerts', () => {
describe('init', () => {
it('calls handshake.init when connected & got camera stream', async () => {
const promise = CallActions.init(store.dispatch, store.getState)
const promise = callActions.init()
socket.emit('connect')
expect(store.getActions()).toEqual([{
type: constants.INIT_PENDING,
@ -60,8 +62,7 @@ describe('reducers/alerts', () => {
})
it('calls dispatches disconnect message on disconnect', async () => {
const promise = CallActions.init(store.dispatch, store.getState)
const promise = callActions.init()
socket.emit('connect')
socket.emit('disconnect')
expect(store.getActions()).toEqual([{
@ -102,7 +103,7 @@ describe('reducers/alerts', () => {
it('dispatches alert when failed to get media stream', async () => {
(getUserMedia as any).fail(true)
const promise = CallActions.init(store.dispatch, store.getState)
const promise = callActions.init()
socket.emit('connect')
await promise
})

View File

@ -4,8 +4,7 @@ import * as StreamActions from './StreamActions'
import * as constants from '../constants'
import socket from '../socket'
import { callId, getUserMedia } from '../window'
import { Dispatch } from 'redux'
import { GetState } from '../store'
import { Dispatch, GetState } from '../store'
export interface InitAction {
type: 'INIT'
@ -20,7 +19,7 @@ const initialize = (): InitializeAction => ({
type: 'INIT',
})
export const init = async (dispatch: Dispatch, getState: GetState) => {
export const init = () => async (dispatch: Dispatch, getState: GetState) => {
dispatch(initialize())
const socket = await connect(dispatch)
@ -39,10 +38,10 @@ export async function connect (dispatch: Dispatch) {
resolve(socket)
})
socket.on('connect', () => {
NotifyActions.warning('Connected to server socket')(dispatch)
dispatch(NotifyActions.warning('Connected to server socket'))
})
socket.on('disconnect', () => {
NotifyActions.error('Server socket disconnected')(dispatch)
dispatch(NotifyActions.error('Server socket disconnected'))
})
})
}

View File

@ -1,7 +1,7 @@
import * as constants from '../constants'
import * as ChatActions from './ChatActions'
import { Dispatch } from 'redux'
import { Dispatch } from 'redux'
import _ from 'underscore'
import { ThunkResult } from '../store'
const TIMEOUT = 5000
@ -13,21 +13,29 @@ function format (string: string, args: string[]) {
export type NotifyType = 'info' | 'warning' | 'error'
const _notify = (type: NotifyType, args: string[]) => (dispatch: Dispatch) => {
function notify(dispatch: Dispatch, type: NotifyType, args: string[]) {
const string = args[0] || ''
const message = format(string, Array.prototype.slice.call(args, 1))
const id = _.uniqueId('notification')
const payload: Notification = { id, type, message }
dispatch(addNotification(payload))
dispatch(ChatActions.addMessage({
userId: '[PeerCalls]',
message,
timestamp: new Date().toLocaleString(),
image: undefined,
}))
setTimeout(() => {
dispatch(dismissNotification(id))
}, TIMEOUT)
return addNotification(payload)
}
export const info = (...args: any[]): ThunkResult<NotificationAddAction> => {
return dispatch => notify(dispatch, 'info', args)
}
export const warning = (...args: any[]): ThunkResult<NotificationAddAction> => {
return dispatch => notify(dispatch, 'warning', args)
}
export const error = (...args: any[]): ThunkResult<NotificationAddAction> => {
return dispatch => notify(dispatch, 'error', args)
}
function addNotification(payload: Notification): NotificationAddAction {
@ -60,18 +68,6 @@ export interface NotificationDismissAction {
payload: { id: string }
}
export function info (...args: any[]) {
return (dispatch: Dispatch) => _notify('info', args)(dispatch)
}
export function warning (...args: any[]) {
return (dispatch: Dispatch) => _notify('warning', args)(dispatch)
}
export function error (...args: any[]) {
return (dispatch: Dispatch) => _notify('error', args)(dispatch)
}
export interface NotificationClearAction {
type: 'NOTIFY_CLEAR'
}

View File

@ -167,9 +167,9 @@ describe('PeerActions', () => {
PeerActions.sendMessage({ payload: 'test', type: 'text' })(
dispatch, getState)
const { peers } = store.getState()
expect(peers['user2'].send.mock.calls)
expect((peers['user2'].send as jest.Mock).mock.calls)
.toEqual([[ '{"payload":"test","type":"text"}' ]])
expect(peers['user3'].send.mock.calls)
expect((peers['user3'].send as jest.Mock).mock.calls)
.toEqual([[ '{"payload":"test","type":"text"}' ]])
})

View File

@ -6,7 +6,7 @@ import Peer from 'simple-peer'
import _ from 'underscore'
import _debug from 'debug'
import { play, iceServers } from '../window'
import { Dispatch } from 'redux'
import { Dispatch, GetState } from '../store'
const debug = _debug('peercalls')
@ -14,8 +14,6 @@ export interface Peers {
[id: string]: Peer.Instance
}
export type GetState = () => { peers: Peers }
export interface PeerHandlerOptions {
socket: SocketIOClient.Socket
user: { id: string }
@ -38,7 +36,7 @@ class PeerHandler {
handleError = (err: Error) => {
const { dispatch, getState, user } = this
debug('peer: %s, error %s', user.id, err.stack)
NotifyActions.error('A peer connection error occurred')(dispatch)
dispatch(NotifyActions.error('A peer connection error occurred'))
const peer = getState().peers[user.id]
peer && peer.destroy()
dispatch(removePeer(user.id))
@ -53,7 +51,7 @@ class PeerHandler {
handleConnect = () => {
const { dispatch, user } = this
debug('peer: %s, connect', user.id)
NotifyActions.warning('Peer connection established')(dispatch)
dispatch(NotifyActions.warning('Peer connection established'))
play()
}
handleStream = (stream: MediaStream) => {
@ -64,10 +62,10 @@ class PeerHandler {
stream,
}))
}
handleData = (object: any) => {
handleData = (buffer: ArrayBuffer) => {
const { dispatch, user } = this
const message = JSON.parse(new window.TextDecoder('utf-8').decode(object))
debug('peer: %s, message: %o', user.id, object)
const message = JSON.parse(new window.TextDecoder('utf-8').decode(buffer))
debug('peer: %s, message: %o', user.id, buffer)
switch (message.type) {
case 'file':
dispatch(ChatActions.addMessage({
@ -89,7 +87,7 @@ class PeerHandler {
handleClose = () => {
const { dispatch, user } = this
debug('peer: %s, close', user.id)
NotifyActions.error('Peer connection closed')(dispatch)
dispatch(NotifyActions.error('Peer connection closed'))
dispatch(StreamActions.removeStream(user.id))
dispatch(removePeer(user.id))
}
@ -116,11 +114,11 @@ export function createPeer (options: CreatePeerOptions) {
return (dispatch: Dispatch, getState: GetState) => {
const userId = user.id
debug('create peer: %s, stream:', userId, stream)
NotifyActions.warning('Connecting to peer...')(dispatch)
dispatch(NotifyActions.warning('Connecting to peer...'))
const oldPeer = getState().peers[userId]
if (oldPeer) {
NotifyActions.info('Cleaning up old connection...')(dispatch)
dispatch(NotifyActions.info('Cleaning up old connection...'))
oldPeer.destroy()
dispatch(removePeer(userId))
}
@ -244,7 +242,7 @@ export const sendFile = (file: File) =>
async (dispatch: Dispatch, getState: GetState) => {
const { name, size, type } = file
if (!window.FileReader) {
NotifyActions.error('File API is not supported by your browser')(dispatch)
dispatch(NotifyActions.error('File API is not supported by your browser'))
return
}
const reader = new window.FileReader()

View File

@ -3,8 +3,8 @@ import * as PeerActions from '../actions/PeerActions'
import * as constants from '../constants'
import _ from 'underscore'
import _debug from 'debug'
import { Dispatch } from 'redux'
import { SignalData } from 'simple-peer'
import { Dispatch, GetState } from '../store'
const debug = _debug('peercalls')
@ -13,7 +13,7 @@ export interface SocketHandlerOptions {
roomName: string
stream?: MediaStream
dispatch: Dispatch
getState: PeerActions.GetState
getState: GetState
}
export interface SignalOptions {
@ -31,7 +31,7 @@ class SocketHandler {
roomName: string
stream?: MediaStream
dispatch: Dispatch
getState: PeerActions.GetState
getState: GetState
constructor (options: SocketHandlerOptions) {
this.socket = options.socket
@ -50,8 +50,8 @@ class SocketHandler {
handleUsers = ({ initiator, users }: UsersOptions) => {
const { socket, stream, dispatch, getState } = this
debug('socket users: %o', users)
NotifyActions.info('Connected users: {0}', users.length)(dispatch)
const { peers } = getState()
this.dispatch(NotifyActions.info('Connected users: {0}', users.length))
const { peers } = this.getState()
users
.filter(user => !peers[user.id] && user.id !== socket.id)
@ -78,7 +78,7 @@ export interface HandshakeOptions {
export function handshake (options: HandshakeOptions) {
const { socket, roomName, stream } = options
return (dispatch: Dispatch, getState: PeerActions.GetState) => {
return (dispatch: Dispatch, getState: GetState) => {
const handler = new SocketHandler({
socket,
roomName,
@ -92,7 +92,7 @@ export function handshake (options: HandshakeOptions) {
debug('socket.id: %s', socket.id)
debug('emit ready for room: %s', roomName)
NotifyActions.info('Ready for connections')(dispatch)
dispatch(NotifyActions.info('Ready for connections'))
socket.emit('ready', roomName)
}
}

View File

@ -0,0 +1,3 @@
export * from './action'
export * from './middleware'
export * from './reducer'

View File

@ -2,98 +2,114 @@ jest.mock('../actions/CallActions')
jest.mock('../socket')
jest.mock('../window')
import * as constants from '../constants'
import App from './App'
import React from 'react'
import ReactDOM from 'react-dom'
import TestUtils from 'react-dom/test-utils'
import configureStore from 'redux-mock-store'
import reducers from '../reducers'
import { MediaStream } from '../window'
import { Provider } from 'react-redux'
import { AnyAction, applyMiddleware, createStore } from 'redux'
import { init } from '../actions/CallActions'
import { middlewares } from '../store'
import { Alert } from '../actions/NotifyActions'
import * as constants from '../constants'
import reducers from '../reducers'
import { middlewares, State, Store } from '../store'
import { MediaStream } from '../window'
import App from './App'
describe('App', () => {
const initAction = { type: 'INIT' }
let state
let store: Store
let state: Partial<State>
let dispatchSpy: jest.SpyInstance<AnyAction, AnyAction[]>
beforeEach(() => {
init.mockReturnValue(initAction)
state = reducers()
state = {};
(init as jest.Mock).mockReturnValue(initAction)
dispatchSpy = jest.spyOn(store, 'dispatch')
window.HTMLMediaElement.prototype.play = jest.fn()
})
let component, node, store
function render () {
store = configureStore(middlewares)(state)
component = TestUtils.renderIntoDocument(
<Provider store={store}>
<App />
</Provider>
afterEach(() => {
dispatchSpy.mockReset()
dispatchSpy.mockRestore()
})
let node: Element
async function render () {
store = createStore(
reducers,
state,
applyMiddleware(...middlewares),
)
node = ReactDOM.findDOMNode(component)
const div = document.createElement('div')
await new Promise<HTMLDivElement>(resolve => {
ReactDOM.render(
<Provider store={store}>
<div ref={app => resolve(app!)}>
<App />
</div>
</Provider>,
div,
)
})
}
describe('render', () => {
it('renders without issues', () => {
render()
expect(node).toBeTruthy()
expect(init.mock.calls.length).toBe(1)
expect((init as jest.Mock).mock.calls.length).toBe(1)
})
})
describe('state', () => {
let alert
let alert: Alert
beforeEach(() => {
state.streams = {
test: {
mediaStream: new MediaStream(),
url: 'blob://'
}
url: 'blob://',
},
}
state.peers = {
test: {}
test: {} as any,
}
state.notifications = state.notifications.merge({
state.notifications = {
'notification1': {
id: 'notification1',
message: 'test',
type: 'warning'
}
})
const alerts = state.alerts.asMutable()
alert = {
type: 'warning',
},
}
state.alerts = [{
dismissable: true,
action: 'Dismiss',
message: 'test alert'
}
alerts.push(alert)
state.alerts = alerts
message: 'test alert',
type: 'info',
}]
render()
store.clearActions()
})
describe('alerts', () => {
it('can be dismissed', () => {
const dismiss = node.querySelector('.action-alert-dismiss')
const dismiss = node.querySelector('.action-alert-dismiss')!
TestUtils.Simulate.click(dismiss)
expect(store.getActions()).toEqual([{
expect(dispatchSpy.mock.calls).toEqual([[{
type: constants.ALERT_DISMISS,
payload: alert
}])
payload: alert,
}]])
})
})
describe('video', () => {
it('can be activated', () => {
const video = node.querySelector('video')
const video = node.querySelector('video')!
TestUtils.Simulate.click(video)
expect(store.getActions()).toEqual([{
expect(dispatchSpy.mock.calls).toEqual([[{
type: constants.ACTIVE_TOGGLE,
payload: { userId: constants.ME }
}])
payload: { userId: constants.ME },
}]])
})
})

View File

@ -1,6 +1,6 @@
import * as NotifyActions from '../actions/NotifyActions'
import _ from 'underscore'
import { applyMiddleware, createStore, Store } from 'redux'
import { applyMiddleware, createStore, Store, bindActionCreators } from 'redux'
import { create } from '../middlewares'
import reducers from './index'
@ -9,21 +9,24 @@ jest.useFakeTimers()
describe('reducers/alerts', () => {
let store: Store
let notifyActions: typeof NotifyActions
beforeEach(() => {
store = createStore(
reducers,
applyMiddleware(...create()),
)
notifyActions = bindActionCreators(NotifyActions, store.dispatch)
})
describe('clearAlert', () => {
[true, false].forEach(dismissable => {
beforeEach(() => {
store.dispatch(NotifyActions.clearAlerts())
notifyActions.clearAlerts()
})
it('adds alert to store', () => {
store.dispatch(NotifyActions.alert('test', dismissable))
notifyActions.alert('test', dismissable)
expect(store.getState().alerts).toEqual([{
action: dismissable ? 'Dismiss' : undefined,
dismissable,
@ -69,7 +72,7 @@ describe('reducers/alerts', () => {
describe(type, () => {
beforeEach(() => {
NotifyActions[type]('Hi {0}!', 'John')(store.dispatch)
notifyActions[type]('Hi {0}!', 'John')
})
it('adds a notification', () => {
@ -86,7 +89,7 @@ describe('reducers/alerts', () => {
})
it('does not fail when no arguments', () => {
NotifyActions[type]()(store.dispatch)
notifyActions[type]()
})
})
@ -96,9 +99,9 @@ describe('reducers/alerts', () => {
describe('clear', () => {
it('clears all alerts', () => {
NotifyActions.info('Hi {0}!', 'John')(store.dispatch)
NotifyActions.warning('Hi {0}!', 'John')(store.dispatch)
NotifyActions.error('Hi {0}!', 'John')(store.dispatch)
notifyActions.info('Hi {0}!', 'John')
notifyActions.warning('Hi {0}!', 'John')
notifyActions.error('Hi {0}!', 'John')
store.dispatch(NotifyActions.clear())
expect(store.getState().notifications).toEqual({})
})

View File

@ -1,14 +1,28 @@
import * as constants from '../constants'
import { Message, MessageAddAction } from '../actions/ChatActions'
import { NotificationAddAction } from '../actions/NotifyActions'
export type MessagesState = Message[]
const defaultState: MessagesState = []
function convertNotificationToMessage(action: NotificationAddAction): Message {
return {
userId: '[PeerCalls]',
message: action.payload.message,
timestamp: new Date().toLocaleString(),
}
}
export default function messages (
state = defaultState, action: MessageAddAction,
) {
switch (action && action.type) {
state = defaultState, action: MessageAddAction | NotificationAddAction,
): MessagesState {
switch (action.type) {
case constants.NOTIFY:
return [
...state,
convertNotificationToMessage(action),
]
case constants.MESSAGE_ADD:
return [...state, action.payload]
default:

View File

@ -3,13 +3,14 @@ import _ from 'underscore'
import Peer from 'simple-peer'
import { PeerAction } from '../actions/PeerActions'
export interface PeersState {
[userId: string]: Peer.Instance
}
export type PeersState = Record<string, Peer.Instance>
const defaultState: PeersState = {}
export default function peers (state = defaultState, action: PeerAction) {
export default function peers(
state = defaultState,
action: PeerAction,
): PeersState {
switch (action.type) {
case constants.PEER_ADD:
return {

View File

@ -1,13 +1,16 @@
import { Action, applyMiddleware, createStore as _createStore, Store as ReduxStore } from 'redux'
import { ThunkAction, ThunkDispatch } from 'redux-thunk'
import { create } from './middlewares'
import { middleware as asyncMiddleware }from './async'
import reducers from './reducers'
import { applyMiddleware, createStore as _createStore, Store as ReduxStore } from 'redux'
export const middlewares = create(
window.localStorage && window.localStorage.log,
)
export const createStore = () => _createStore(
reducers,
applyMiddleware(...middlewares),
applyMiddleware(...middlewares, asyncMiddleware),
)
export default createStore()
@ -17,3 +20,6 @@ export type Store = ReturnType<typeof createStore>
type TGetState<T> = T extends ReduxStore<infer State> ? State : never
export type State = TGetState<Store>
export type GetState = () => State
export type Dispatch = ThunkDispatch<State, undefined, Action>
export type ThunkResult<R> = ThunkAction<R, State, undefined, Action>

View File

@ -1,5 +1,4 @@
#!/usr/bin/env node
'use strict'
if (!process.env.DEBUG) {
process.env.DEBUG = 'peercalls'
}