Fix bug: Improve code to use ref and test units
This commit is contained in:
parent
4e6657f19a
commit
b91a1f2f81
@ -4,7 +4,18 @@ export const createObjectURL = jest.fn()
|
|||||||
.mockImplementation(object => 'blob://' + String(object))
|
.mockImplementation(object => 'blob://' + String(object))
|
||||||
export const revokeObjectURL = jest.fn()
|
export const revokeObjectURL = jest.fn()
|
||||||
|
|
||||||
export class MediaStream {}
|
export class MediaStream {
|
||||||
|
getVideoTracks () {
|
||||||
|
return [{
|
||||||
|
enabled: true
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
getAudioTracks () {
|
||||||
|
return [{
|
||||||
|
enabled: true
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
export function getUserMedia () {
|
export function getUserMedia () {
|
||||||
return !getUserMedia.shouldFail
|
return !getUserMedia.shouldFail
|
||||||
? Promise.resolve(getUserMedia.stream)
|
? Promise.resolve(getUserMedia.stream)
|
||||||
|
|||||||
@ -23,6 +23,12 @@ export default class App extends React.PureComponent {
|
|||||||
streams: PropTypes.objectOf(StreamPropType).isRequired,
|
streams: PropTypes.objectOf(StreamPropType).isRequired,
|
||||||
toggleActive: PropTypes.func.isRequired
|
toggleActive: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
this.state = {
|
||||||
|
videos: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { init } = this.props
|
const { init } = this.props
|
||||||
init()
|
init()
|
||||||
@ -41,17 +47,29 @@ export default class App extends React.PureComponent {
|
|||||||
streams
|
streams
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
|
const { videos } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<Toolbar messages={messages} stream={streams[constants.ME]} />
|
<Toolbar
|
||||||
|
chatRef={this.chatRef}
|
||||||
|
messages={messages}
|
||||||
|
stream={streams[constants.ME]}
|
||||||
|
ref={node => { this.toolbarRef = node }}
|
||||||
|
/>
|
||||||
<Alerts alerts={alerts} dismiss={dismissAlert} />
|
<Alerts alerts={alerts} dismiss={dismissAlert} />
|
||||||
<Notifications notifications={notifications} />
|
<Notifications notifications={notifications} />
|
||||||
<div id="chat">
|
<div id="chat" ref={node => { this.chatRef = node }}>
|
||||||
<Chat messages={messages} />
|
<Chat toolbarRef={this.toolbarRef} messages={messages} />
|
||||||
<Input notify={notify} sendMessage={sendMessage} />
|
<Input
|
||||||
|
videos={videos}
|
||||||
|
notify={notify}
|
||||||
|
sendMessage={sendMessage}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="videos">
|
<div className="videos">
|
||||||
<Video
|
<Video
|
||||||
|
videos={videos}
|
||||||
active={active === constants.ME}
|
active={active === constants.ME}
|
||||||
onClick={toggleActive}
|
onClick={toggleActive}
|
||||||
stream={streams[constants.ME]}
|
stream={streams[constants.ME]}
|
||||||
|
|||||||
@ -10,42 +10,15 @@ export const MessagePropTypes = PropTypes.shape({
|
|||||||
|
|
||||||
export default class Chat extends React.PureComponent {
|
export default class Chat extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
toolbarRef: PropTypes.object.isRequired,
|
||||||
messages: PropTypes.arrayOf(MessagePropTypes).isRequired
|
messages: PropTypes.arrayOf(MessagePropTypes).isRequired
|
||||||
}
|
}
|
||||||
handleCloseChat = e => {
|
handleCloseChat = e => {
|
||||||
document.getElementById('chat').classList.remove('show')
|
const { toolbarRef } = this.props
|
||||||
document.querySelector('.toolbar .chat').classList.remove('on')
|
toolbarRef.chatButton.click()
|
||||||
}
|
}
|
||||||
scrollToBottom = () => {
|
scrollToBottom = () => {
|
||||||
// this.chatScroll.scrollTop = this.chatScroll.scrollHeight
|
this.chatScroll.scrollTop = this.chatScroll.scrollHeight
|
||||||
|
|
||||||
const duration = 300
|
|
||||||
const start = this.chatScroll.scrollTop
|
|
||||||
const end = this.chatScroll.scrollHeight
|
|
||||||
const change = end - start
|
|
||||||
const increment = 20
|
|
||||||
|
|
||||||
const easeInOut = (currentTime, start, change, duration) => {
|
|
||||||
currentTime /= duration / 2
|
|
||||||
if (currentTime < 1) {
|
|
||||||
return change / 2 * currentTime * currentTime + start
|
|
||||||
}
|
|
||||||
currentTime -= 1
|
|
||||||
return -change / 2 * (currentTime * (currentTime - 2) - 1) + start
|
|
||||||
}
|
|
||||||
|
|
||||||
const animate = elapsedTime => {
|
|
||||||
elapsedTime += increment
|
|
||||||
const position = easeInOut(elapsedTime, start, change, duration)
|
|
||||||
this.chatScroll.scrollTop = position
|
|
||||||
if (elapsedTime < duration) {
|
|
||||||
setTimeout(() => {
|
|
||||||
animate(elapsedTime)
|
|
||||||
}, increment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
animate(0)
|
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.scrollToBottom()
|
this.scrollToBottom()
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import socket from '../socket.js'
|
|||||||
|
|
||||||
export default class Input extends React.PureComponent {
|
export default class Input extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
videos: PropTypes.object.isRequired,
|
||||||
notify: PropTypes.func.isRequired,
|
notify: PropTypes.func.isRequired,
|
||||||
sendMessage: PropTypes.func.isRequired
|
sendMessage: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
@ -29,7 +30,7 @@ export default class Input extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
submit = () => {
|
submit = () => {
|
||||||
const { notify, sendMessage } = this.props
|
const { videos, notify, sendMessage } = this.props
|
||||||
const { message } = this.state
|
const { message } = this.state
|
||||||
if (message) {
|
if (message) {
|
||||||
notify('You: ' + message)
|
notify('You: ' + message)
|
||||||
@ -41,13 +42,15 @@ export default class Input extends React.PureComponent {
|
|||||||
|
|
||||||
// take snapshoot
|
// take snapshoot
|
||||||
try {
|
try {
|
||||||
const video = document.getElementById(`video-${userId}`)
|
const video = videos[userId]
|
||||||
const canvas = document.createElement('canvas')
|
if (video) {
|
||||||
canvas.height = video.videoHeight
|
const canvas = document.createElement('canvas')
|
||||||
canvas.width = video.videoWidth
|
canvas.height = video.videoHeight
|
||||||
const avatar = canvas.getContext('2d')
|
canvas.width = video.videoWidth
|
||||||
avatar.drawImage(video, 0, 0, canvas.width, canvas.height)
|
const avatar = canvas.getContext('2d')
|
||||||
image = canvas.toDataURL()
|
avatar.drawImage(video, 0, 0, canvas.width, canvas.height)
|
||||||
|
image = canvas.toDataURL()
|
||||||
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
const payload = { userId, message, timestamp, image }
|
const payload = { userId, message, timestamp, image }
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { StreamPropType } from './Video.js'
|
|||||||
|
|
||||||
export default class Toolbar extends React.PureComponent {
|
export default class Toolbar extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
chatRef: PropTypes.object.isRequired,
|
||||||
messages: PropTypes.arrayOf(MessagePropTypes).isRequired,
|
messages: PropTypes.arrayOf(MessagePropTypes).isRequired,
|
||||||
stream: StreamPropType
|
stream: StreamPropType
|
||||||
}
|
}
|
||||||
@ -16,36 +17,36 @@ export default class Toolbar extends React.PureComponent {
|
|||||||
totalMessages: 0
|
totalMessages: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleChatClick = e => {
|
handleChatClick = () => {
|
||||||
const { messages } = this.props
|
const { chatRef, messages } = this.props
|
||||||
document.getElementById('chat').classList.toggle('show')
|
chatRef.classList.toggle('show')
|
||||||
e.currentTarget.classList.toggle('on')
|
this.chatButton.classList.toggle('on')
|
||||||
this.setState({
|
this.setState({
|
||||||
isChatOpen: document.getElementById('chat').classList.contains('show'),
|
isChatOpen: chatRef.classList.contains('show'),
|
||||||
totalMessages: messages.length
|
totalMessages: messages.length
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
handleMicClick = e => {
|
handleMicClick = () => {
|
||||||
const { stream } = this.props
|
const { stream } = this.props
|
||||||
stream.mediaStream.getAudioTracks().forEach(track => {
|
stream.mediaStream.getAudioTracks().forEach(track => {
|
||||||
track.enabled = !track.enabled
|
track.enabled = !track.enabled
|
||||||
})
|
})
|
||||||
e.currentTarget.classList.toggle('on')
|
this.mixButton.classList.toggle('on')
|
||||||
}
|
}
|
||||||
handleCamClick = e => {
|
handleCamClick = () => {
|
||||||
const { stream } = this.props
|
const { stream } = this.props
|
||||||
stream.mediaStream.getVideoTracks().forEach(track => {
|
stream.mediaStream.getVideoTracks().forEach(track => {
|
||||||
track.enabled = !track.enabled
|
track.enabled = !track.enabled
|
||||||
})
|
})
|
||||||
e.currentTarget.classList.toggle('on')
|
this.camButton.classList.toggle('on')
|
||||||
}
|
}
|
||||||
handleFullscreenClick = e => {
|
handleFullscreenClick = () => {
|
||||||
if (screenfull.enabled) {
|
if (screenfull.enabled) {
|
||||||
screenfull.toggle(e.target)
|
screenfull.toggle(this.fullscreenButton)
|
||||||
e.currentTarget.classList.toggle('on')
|
this.fullscreenButton.classList.toggle('on')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleHangoutClick = e => {
|
handleHangoutClick = () => {
|
||||||
window.location.href = '/'
|
window.location.href = '/'
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
@ -55,6 +56,7 @@ export default class Toolbar extends React.PureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className="toolbar active">
|
<div className="toolbar active">
|
||||||
<div onClick={this.handleChatClick}
|
<div onClick={this.handleChatClick}
|
||||||
|
ref={node => { this.chatButton = node }}
|
||||||
className="button chat"
|
className="button chat"
|
||||||
data-blink={messages.length !== totalMessages && !isChatOpen}
|
data-blink={messages.length !== totalMessages && !isChatOpen}
|
||||||
title="Chat"
|
title="Chat"
|
||||||
@ -65,6 +67,7 @@ export default class Toolbar extends React.PureComponent {
|
|||||||
{stream && (
|
{stream && (
|
||||||
<div>
|
<div>
|
||||||
<div onClick={this.handleMicClick}
|
<div onClick={this.handleMicClick}
|
||||||
|
ref={node => { this.mixButton = node }}
|
||||||
className="button mute-audio"
|
className="button mute-audio"
|
||||||
title="Mute audio"
|
title="Mute audio"
|
||||||
>
|
>
|
||||||
@ -72,6 +75,7 @@ export default class Toolbar extends React.PureComponent {
|
|||||||
<span className="off icon icon-mic" />
|
<span className="off icon icon-mic" />
|
||||||
</div>
|
</div>
|
||||||
<div onClick={this.handleCamClick}
|
<div onClick={this.handleCamClick}
|
||||||
|
ref={node => { this.camButton = node }}
|
||||||
className="button mute-video"
|
className="button mute-video"
|
||||||
title="Mute video"
|
title="Mute video"
|
||||||
>
|
>
|
||||||
@ -82,6 +86,7 @@ export default class Toolbar extends React.PureComponent {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div onClick={this.handleFullscreenClick}
|
<div onClick={this.handleFullscreenClick}
|
||||||
|
ref={node => { this.fullscreenButton = node }}
|
||||||
className="button fullscreen"
|
className="button fullscreen"
|
||||||
title="Enter fullscreen"
|
title="Enter fullscreen"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export const StreamPropType = PropTypes.shape({
|
|||||||
|
|
||||||
export default class Video extends React.PureComponent {
|
export default class Video extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
videos: PropTypes.object.isRequired,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
active: PropTypes.bool.isRequired,
|
active: PropTypes.bool.isRequired,
|
||||||
stream: StreamPropType,
|
stream: StreamPropType,
|
||||||
@ -29,7 +30,7 @@ export default class Video extends React.PureComponent {
|
|||||||
this.componentDidUpdate()
|
this.componentDidUpdate()
|
||||||
}
|
}
|
||||||
componentDidUpdate () {
|
componentDidUpdate () {
|
||||||
const { stream } = this.props
|
const { videos, stream } = this.props
|
||||||
const { video } = this.refs
|
const { video } = this.refs
|
||||||
const mediaStream = stream && stream.mediaStream
|
const mediaStream = stream && stream.mediaStream
|
||||||
const url = stream && stream.url
|
const url = stream && stream.url
|
||||||
@ -40,6 +41,9 @@ export default class Video extends React.PureComponent {
|
|||||||
} else if (video.src !== url) {
|
} else if (video.src !== url) {
|
||||||
video.src = url
|
video.src = url
|
||||||
}
|
}
|
||||||
|
if (socket.id) {
|
||||||
|
videos[socket.id] = video
|
||||||
|
}
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const { active } = this.props
|
const { active } = this.props
|
||||||
|
|||||||
@ -5,12 +5,14 @@ import TestUtils from 'react-dom/test-utils'
|
|||||||
|
|
||||||
describe('components/Input', () => {
|
describe('components/Input', () => {
|
||||||
|
|
||||||
let component, node, notify, sendMessage
|
let component, node, videos, notify, sendMessage
|
||||||
function render () {
|
function render () {
|
||||||
|
videos = {}
|
||||||
notify = jest.fn()
|
notify = jest.fn()
|
||||||
sendMessage = jest.fn()
|
sendMessage = jest.fn()
|
||||||
component = TestUtils.renderIntoDocument(
|
component = TestUtils.renderIntoDocument(
|
||||||
<Input
|
<Input
|
||||||
|
videos={videos}
|
||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
notify={notify}
|
notify={notify}
|
||||||
/>
|
/>
|
||||||
|
|||||||
87
src/client/components/__tests__/Toolbar-test.js
Normal file
87
src/client/components/__tests__/Toolbar-test.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
jest.mock('../../window.js')
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import TestUtils from 'react-dom/test-utils'
|
||||||
|
import Toolbar from '../Toolbar.js'
|
||||||
|
import { MediaStream } from '../../window.js'
|
||||||
|
|
||||||
|
describe('components/Video', () => {
|
||||||
|
|
||||||
|
class ToolbarWrapper extends React.PureComponent {
|
||||||
|
static propTypes = Toolbar.propTypes
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
this.state = {}
|
||||||
|
}
|
||||||
|
render () {
|
||||||
|
return <Toolbar
|
||||||
|
chatRef={this.props.chatRef}
|
||||||
|
messages={this.props.messages}
|
||||||
|
stream={this.state.stream || this.props.stream}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let component, node, chatRef, mediaStream, url
|
||||||
|
function render () {
|
||||||
|
mediaStream = new MediaStream()
|
||||||
|
chatRef = ReactDOM.findDOMNode(
|
||||||
|
TestUtils.renderIntoDocument(<div />)
|
||||||
|
)
|
||||||
|
component = TestUtils.renderIntoDocument(
|
||||||
|
<ToolbarWrapper
|
||||||
|
chatRef={chatRef}
|
||||||
|
messages={[]}
|
||||||
|
stream={{ mediaStream, url }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
node = ReactDOM.findDOMNode(component)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('render', () => {
|
||||||
|
it('should not fail', () => {
|
||||||
|
render()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('handleChatClick', () => {
|
||||||
|
it('toggle chat', () => {
|
||||||
|
const button = node.querySelector('.chat')
|
||||||
|
TestUtils.Simulate.click(button)
|
||||||
|
expect(button.classList.contains('on')).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('handleMicClick', () => {
|
||||||
|
it('toggle mic', () => {
|
||||||
|
const button = node.querySelector('.mute-audio')
|
||||||
|
TestUtils.Simulate.click(button)
|
||||||
|
expect(button.classList.contains('on')).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('handleCamClick', () => {
|
||||||
|
it('toggle cam', () => {
|
||||||
|
const button = node.querySelector('.mute-video')
|
||||||
|
TestUtils.Simulate.click(button)
|
||||||
|
expect(button.classList.contains('on')).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('handleFullscreenClick', () => {
|
||||||
|
it('toggle fullscreen', () => {
|
||||||
|
const button = node.querySelector('.fullscreen')
|
||||||
|
TestUtils.Simulate.click(button)
|
||||||
|
expect(button.classList.contains('on')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('handleHangoutClick', () => {
|
||||||
|
it('hangout', () => {
|
||||||
|
const button = node.querySelector('.hangup')
|
||||||
|
TestUtils.Simulate.click(button)
|
||||||
|
expect(window.location.href).toBe('http://localhost/')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
@ -14,6 +14,7 @@ describe('components/Video', () => {
|
|||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
return <Video
|
return <Video
|
||||||
|
videos={this.props.videos}
|
||||||
active={this.props.active}
|
active={this.props.active}
|
||||||
stream={this.state.stream || this.props.stream}
|
stream={this.state.stream || this.props.stream}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
@ -22,12 +23,14 @@ describe('components/Video', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let component, video, onClick, mediaStream, url
|
let component, videos, video, onClick, mediaStream, url
|
||||||
function render () {
|
function render () {
|
||||||
|
videos = {}
|
||||||
onClick = jest.fn()
|
onClick = jest.fn()
|
||||||
mediaStream = new MediaStream()
|
mediaStream = new MediaStream()
|
||||||
component = TestUtils.renderIntoDocument(
|
component = TestUtils.renderIntoDocument(
|
||||||
<VideoWrapper
|
<VideoWrapper
|
||||||
|
videos={videos}
|
||||||
active
|
active
|
||||||
stream={{ mediaStream, url }}
|
stream={{ mediaStream, url }}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user