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))
|
||||
export const revokeObjectURL = jest.fn()
|
||||
|
||||
export class MediaStream {}
|
||||
export class MediaStream {
|
||||
getVideoTracks () {
|
||||
return [{
|
||||
enabled: true
|
||||
}]
|
||||
}
|
||||
getAudioTracks () {
|
||||
return [{
|
||||
enabled: true
|
||||
}]
|
||||
}
|
||||
}
|
||||
export function getUserMedia () {
|
||||
return !getUserMedia.shouldFail
|
||||
? Promise.resolve(getUserMedia.stream)
|
||||
|
||||
@ -23,6 +23,12 @@ export default class App extends React.PureComponent {
|
||||
streams: PropTypes.objectOf(StreamPropType).isRequired,
|
||||
toggleActive: PropTypes.func.isRequired
|
||||
}
|
||||
constructor () {
|
||||
super()
|
||||
this.state = {
|
||||
videos: {}
|
||||
}
|
||||
}
|
||||
componentDidMount () {
|
||||
const { init } = this.props
|
||||
init()
|
||||
@ -41,17 +47,29 @@ export default class App extends React.PureComponent {
|
||||
streams
|
||||
} = this.props
|
||||
|
||||
const { videos } = this.state
|
||||
|
||||
return (
|
||||
<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} />
|
||||
<Notifications notifications={notifications} />
|
||||
<div id="chat">
|
||||
<Chat messages={messages} />
|
||||
<Input notify={notify} sendMessage={sendMessage} />
|
||||
<div id="chat" ref={node => { this.chatRef = node }}>
|
||||
<Chat toolbarRef={this.toolbarRef} messages={messages} />
|
||||
<Input
|
||||
videos={videos}
|
||||
notify={notify}
|
||||
sendMessage={sendMessage}
|
||||
/>
|
||||
</div>
|
||||
<div className="videos">
|
||||
<Video
|
||||
videos={videos}
|
||||
active={active === constants.ME}
|
||||
onClick={toggleActive}
|
||||
stream={streams[constants.ME]}
|
||||
|
||||
@ -10,42 +10,15 @@ export const MessagePropTypes = PropTypes.shape({
|
||||
|
||||
export default class Chat extends React.PureComponent {
|
||||
static propTypes = {
|
||||
toolbarRef: PropTypes.object.isRequired,
|
||||
messages: PropTypes.arrayOf(MessagePropTypes).isRequired
|
||||
}
|
||||
handleCloseChat = e => {
|
||||
document.getElementById('chat').classList.remove('show')
|
||||
document.querySelector('.toolbar .chat').classList.remove('on')
|
||||
const { toolbarRef } = this.props
|
||||
toolbarRef.chatButton.click()
|
||||
}
|
||||
scrollToBottom = () => {
|
||||
// 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)
|
||||
this.chatScroll.scrollTop = this.chatScroll.scrollHeight
|
||||
}
|
||||
componentDidMount () {
|
||||
this.scrollToBottom()
|
||||
|
||||
@ -4,6 +4,7 @@ import socket from '../socket.js'
|
||||
|
||||
export default class Input extends React.PureComponent {
|
||||
static propTypes = {
|
||||
videos: PropTypes.object.isRequired,
|
||||
notify: PropTypes.func.isRequired,
|
||||
sendMessage: PropTypes.func.isRequired
|
||||
}
|
||||
@ -29,7 +30,7 @@ export default class Input extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
submit = () => {
|
||||
const { notify, sendMessage } = this.props
|
||||
const { videos, notify, sendMessage } = this.props
|
||||
const { message } = this.state
|
||||
if (message) {
|
||||
notify('You: ' + message)
|
||||
@ -41,13 +42,15 @@ export default class Input extends React.PureComponent {
|
||||
|
||||
// take snapshoot
|
||||
try {
|
||||
const video = document.getElementById(`video-${userId}`)
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.height = video.videoHeight
|
||||
canvas.width = video.videoWidth
|
||||
const avatar = canvas.getContext('2d')
|
||||
avatar.drawImage(video, 0, 0, canvas.width, canvas.height)
|
||||
image = canvas.toDataURL()
|
||||
const video = videos[userId]
|
||||
if (video) {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.height = video.videoHeight
|
||||
canvas.width = video.videoWidth
|
||||
const avatar = canvas.getContext('2d')
|
||||
avatar.drawImage(video, 0, 0, canvas.width, canvas.height)
|
||||
image = canvas.toDataURL()
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
const payload = { userId, message, timestamp, image }
|
||||
|
||||
@ -6,6 +6,7 @@ import { StreamPropType } from './Video.js'
|
||||
|
||||
export default class Toolbar extends React.PureComponent {
|
||||
static propTypes = {
|
||||
chatRef: PropTypes.object.isRequired,
|
||||
messages: PropTypes.arrayOf(MessagePropTypes).isRequired,
|
||||
stream: StreamPropType
|
||||
}
|
||||
@ -16,36 +17,36 @@ export default class Toolbar extends React.PureComponent {
|
||||
totalMessages: 0
|
||||
}
|
||||
}
|
||||
handleChatClick = e => {
|
||||
const { messages } = this.props
|
||||
document.getElementById('chat').classList.toggle('show')
|
||||
e.currentTarget.classList.toggle('on')
|
||||
handleChatClick = () => {
|
||||
const { chatRef, messages } = this.props
|
||||
chatRef.classList.toggle('show')
|
||||
this.chatButton.classList.toggle('on')
|
||||
this.setState({
|
||||
isChatOpen: document.getElementById('chat').classList.contains('show'),
|
||||
isChatOpen: chatRef.classList.contains('show'),
|
||||
totalMessages: messages.length
|
||||
})
|
||||
}
|
||||
handleMicClick = e => {
|
||||
handleMicClick = () => {
|
||||
const { stream } = this.props
|
||||
stream.mediaStream.getAudioTracks().forEach(track => {
|
||||
track.enabled = !track.enabled
|
||||
})
|
||||
e.currentTarget.classList.toggle('on')
|
||||
this.mixButton.classList.toggle('on')
|
||||
}
|
||||
handleCamClick = e => {
|
||||
handleCamClick = () => {
|
||||
const { stream } = this.props
|
||||
stream.mediaStream.getVideoTracks().forEach(track => {
|
||||
track.enabled = !track.enabled
|
||||
})
|
||||
e.currentTarget.classList.toggle('on')
|
||||
this.camButton.classList.toggle('on')
|
||||
}
|
||||
handleFullscreenClick = e => {
|
||||
handleFullscreenClick = () => {
|
||||
if (screenfull.enabled) {
|
||||
screenfull.toggle(e.target)
|
||||
e.currentTarget.classList.toggle('on')
|
||||
screenfull.toggle(this.fullscreenButton)
|
||||
this.fullscreenButton.classList.toggle('on')
|
||||
}
|
||||
}
|
||||
handleHangoutClick = e => {
|
||||
handleHangoutClick = () => {
|
||||
window.location.href = '/'
|
||||
}
|
||||
render () {
|
||||
@ -55,6 +56,7 @@ export default class Toolbar extends React.PureComponent {
|
||||
return (
|
||||
<div className="toolbar active">
|
||||
<div onClick={this.handleChatClick}
|
||||
ref={node => { this.chatButton = node }}
|
||||
className="button chat"
|
||||
data-blink={messages.length !== totalMessages && !isChatOpen}
|
||||
title="Chat"
|
||||
@ -65,6 +67,7 @@ export default class Toolbar extends React.PureComponent {
|
||||
{stream && (
|
||||
<div>
|
||||
<div onClick={this.handleMicClick}
|
||||
ref={node => { this.mixButton = node }}
|
||||
className="button mute-audio"
|
||||
title="Mute audio"
|
||||
>
|
||||
@ -72,6 +75,7 @@ export default class Toolbar extends React.PureComponent {
|
||||
<span className="off icon icon-mic" />
|
||||
</div>
|
||||
<div onClick={this.handleCamClick}
|
||||
ref={node => { this.camButton = node }}
|
||||
className="button mute-video"
|
||||
title="Mute video"
|
||||
>
|
||||
@ -82,6 +86,7 @@ export default class Toolbar extends React.PureComponent {
|
||||
)}
|
||||
|
||||
<div onClick={this.handleFullscreenClick}
|
||||
ref={node => { this.fullscreenButton = node }}
|
||||
className="button fullscreen"
|
||||
title="Enter fullscreen"
|
||||
>
|
||||
|
||||
@ -11,6 +11,7 @@ export const StreamPropType = PropTypes.shape({
|
||||
|
||||
export default class Video extends React.PureComponent {
|
||||
static propTypes = {
|
||||
videos: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
active: PropTypes.bool.isRequired,
|
||||
stream: StreamPropType,
|
||||
@ -29,7 +30,7 @@ export default class Video extends React.PureComponent {
|
||||
this.componentDidUpdate()
|
||||
}
|
||||
componentDidUpdate () {
|
||||
const { stream } = this.props
|
||||
const { videos, stream } = this.props
|
||||
const { video } = this.refs
|
||||
const mediaStream = stream && stream.mediaStream
|
||||
const url = stream && stream.url
|
||||
@ -40,6 +41,9 @@ export default class Video extends React.PureComponent {
|
||||
} else if (video.src !== url) {
|
||||
video.src = url
|
||||
}
|
||||
if (socket.id) {
|
||||
videos[socket.id] = video
|
||||
}
|
||||
}
|
||||
render () {
|
||||
const { active } = this.props
|
||||
|
||||
@ -5,12 +5,14 @@ import TestUtils from 'react-dom/test-utils'
|
||||
|
||||
describe('components/Input', () => {
|
||||
|
||||
let component, node, notify, sendMessage
|
||||
let component, node, videos, notify, sendMessage
|
||||
function render () {
|
||||
videos = {}
|
||||
notify = jest.fn()
|
||||
sendMessage = jest.fn()
|
||||
component = TestUtils.renderIntoDocument(
|
||||
<Input
|
||||
videos={videos}
|
||||
sendMessage={sendMessage}
|
||||
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 () {
|
||||
return <Video
|
||||
videos={this.props.videos}
|
||||
active={this.props.active}
|
||||
stream={this.state.stream || this.props.stream}
|
||||
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 () {
|
||||
videos = {}
|
||||
onClick = jest.fn()
|
||||
mediaStream = new MediaStream()
|
||||
component = TestUtils.renderIntoDocument(
|
||||
<VideoWrapper
|
||||
videos={videos}
|
||||
active
|
||||
stream={{ mediaStream, url }}
|
||||
onClick={onClick}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user