mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-07-18 10:49:02 +00:00
This reverts commit 16be9dea8349d076ea67783ab25ca261f45b7a2d.
This commit is contained in:
parent
16be9dea83
commit
d475a148b9
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Message from 'components/Message'
|
import Message from 'components/Message'
|
||||||
import Username from 'components/Username'
|
import Username from 'components/Username'
|
||||||
@ -8,143 +8,128 @@ import { getObjectUrl } from 'utils/file'
|
|||||||
|
|
||||||
import T from 'components/T'
|
import T from 'components/T'
|
||||||
|
|
||||||
class Activity extends Component {
|
const FileDisplay = ({ activity: { fileType, encodedFile, fileName, username }, scrollToBottom }) => {
|
||||||
constructor(props) {
|
const zoomableImage = React.useRef(null)
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
const handleImageDisplay = () => {
|
||||||
zoomableImages: [],
|
Zoom(zoomableImage.current)
|
||||||
}
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileDisplay(activity) {
|
if (fileType.match('image.*')) {
|
||||||
const type = activity.fileType
|
return (
|
||||||
if (type.match('image.*')) {
|
<img
|
||||||
|
ref={zoomableImage}
|
||||||
|
className="image-transfer zoomable"
|
||||||
|
src={`data:${fileType};base64,${encodedFile}`}
|
||||||
|
alt={`${fileName} from ${username}`}
|
||||||
|
onLoad={handleImageDisplay}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const Activity = ({ activity, scrollToBottom }) => {
|
||||||
|
switch (activity.type) {
|
||||||
|
case 'TEXT_MESSAGE':
|
||||||
return (
|
return (
|
||||||
<img
|
<Message
|
||||||
ref={c => this._zoomableImage = c}
|
sender={activity.username}
|
||||||
className="image-transfer zoomable"
|
message={activity.text}
|
||||||
src={`data:${activity.fileType};base64,${activity.encodedFile}`}
|
timestamp={activity.timestamp}
|
||||||
alt={`${activity.fileName} from ${activity.username}`}
|
|
||||||
onLoad={this.handleImageDisplay.bind(this)}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
case 'USER_ENTER':
|
||||||
return null
|
return (
|
||||||
}
|
<Notice>
|
||||||
|
<div>
|
||||||
handleImageDisplay() {
|
<T data={{
|
||||||
Zoom(this._zoomableImage)
|
username: <Username key={0} username={activity.username} />
|
||||||
this.props.scrollToBottom()
|
}} path='userJoined'/>
|
||||||
}
|
</div>
|
||||||
|
</Notice>
|
||||||
getActivityComponent(activity) {
|
)
|
||||||
switch (activity.type) {
|
case 'USER_EXIT':
|
||||||
case 'TEXT_MESSAGE':
|
return (
|
||||||
return (
|
<Notice>
|
||||||
<Message
|
<div>
|
||||||
sender={activity.username}
|
<T data={{
|
||||||
message={activity.text}
|
username: <Username key={0} username={activity.username} />
|
||||||
timestamp={activity.timestamp}
|
}} path='userLeft'/>
|
||||||
/>
|
</div>
|
||||||
)
|
</Notice>
|
||||||
case 'USER_ENTER':
|
)
|
||||||
return (
|
case 'TOGGLE_LOCK_ROOM':
|
||||||
<Notice>
|
if (activity.locked) {
|
||||||
<div>
|
|
||||||
<T data={{
|
|
||||||
username: <Username key={0} username={activity.username} />
|
|
||||||
}} path='userJoined'/>
|
|
||||||
</div>
|
|
||||||
</Notice>
|
|
||||||
)
|
|
||||||
case 'USER_EXIT':
|
|
||||||
return (
|
|
||||||
<Notice>
|
|
||||||
<div>
|
|
||||||
<T data={{
|
|
||||||
username: <Username key={0} username={activity.username} />
|
|
||||||
}} path='userLeft'/>
|
|
||||||
</div>
|
|
||||||
</Notice>
|
|
||||||
)
|
|
||||||
case 'TOGGLE_LOCK_ROOM':
|
|
||||||
if (activity.locked) {
|
|
||||||
return (
|
|
||||||
<Notice>
|
|
||||||
<div><T data={{
|
|
||||||
username: <Username key={0} username={activity.username} />
|
|
||||||
}} path='lockedRoom'/></div>
|
|
||||||
</Notice>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Notice>
|
|
||||||
<div><T data={{
|
|
||||||
username: <Username key={0} username={activity.username} />
|
|
||||||
}} path='unlockedRoom'/></div>
|
|
||||||
</Notice>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case 'NOTICE':
|
|
||||||
return (
|
|
||||||
<Notice>
|
|
||||||
<div>{activity.message}</div>
|
|
||||||
</Notice>
|
|
||||||
)
|
|
||||||
case 'CHANGE_USERNAME':
|
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div><T data={{
|
<div><T data={{
|
||||||
oldUsername: <Username key={0} username={activity.currentUsername} />,
|
username: <Username key={0} username={activity.username} />
|
||||||
newUsername: <Username key={1} username={activity.newUsername} />
|
}} path='lockedRoom'/></div>
|
||||||
}} path='nameChange'/>
|
|
||||||
</div>
|
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
)
|
||||||
case 'USER_ACTION':
|
} else {
|
||||||
return (
|
return (
|
||||||
<Notice>
|
<Notice>
|
||||||
<div>* <Username username={activity.username} /> {activity.action}</div>
|
<div><T data={{
|
||||||
|
username: <Username key={0} username={activity.username} />
|
||||||
|
}} path='unlockedRoom'/></div>
|
||||||
</Notice>
|
</Notice>
|
||||||
)
|
)
|
||||||
case 'RECEIVE_FILE':
|
}
|
||||||
const downloadUrl = getObjectUrl(activity.encodedFile, activity.fileType)
|
case 'NOTICE':
|
||||||
return (
|
return (
|
||||||
|
<Notice>
|
||||||
|
<div>{activity.message}</div>
|
||||||
|
</Notice>
|
||||||
|
)
|
||||||
|
case 'CHANGE_USERNAME':
|
||||||
|
return (
|
||||||
|
<Notice>
|
||||||
|
<div><T data={{
|
||||||
|
oldUsername: <Username key={0} username={activity.currentUsername} />,
|
||||||
|
newUsername: <Username key={1} username={activity.newUsername} />
|
||||||
|
}} path='nameChange'/>
|
||||||
|
</div>
|
||||||
|
</Notice>
|
||||||
|
)
|
||||||
|
case 'USER_ACTION':
|
||||||
|
return (
|
||||||
|
<Notice>
|
||||||
|
<div>* <Username username={activity.username} /> {activity.action}</div>
|
||||||
|
</Notice>
|
||||||
|
)
|
||||||
|
case 'RECEIVE_FILE':
|
||||||
|
const downloadUrl = getObjectUrl(activity.encodedFile, activity.fileType)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<T data={{
|
||||||
|
username: <Username key={0} username={activity.username} />,
|
||||||
|
}} path='userSentFile'/>
|
||||||
|
|
||||||
|
<a target="_blank" href={downloadUrl} rel="noopener noreferrer" download={activity.fileName} >
|
||||||
|
<T data={{
|
||||||
|
filename: activity.fileName,
|
||||||
|
}} path='downloadFile'/>
|
||||||
|
</a>
|
||||||
|
<FileDisplay activity={activity} scrollToBottom={scrollToBottom} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
case 'SEND_FILE':
|
||||||
|
const url = getObjectUrl(activity.encodedFile, activity.fileType)
|
||||||
|
return (
|
||||||
|
<Notice>
|
||||||
<div>
|
<div>
|
||||||
<T data={{
|
<T data={{
|
||||||
username: <Username key={0} username={activity.username} />,
|
filename: <a key={0} target="_blank" href={url} rel="noopener noreferrer" download={activity.fileName}>{activity.fileName}</a>,
|
||||||
}} path='userSentFile'/>
|
}} path='sentFile'/>
|
||||||
|
|
||||||
<a target="_blank" href={downloadUrl} rel="noopener noreferrer" download={activity.fileName}>
|
|
||||||
<T data={{
|
|
||||||
filename: activity.fileName,
|
|
||||||
}} path='downloadFile'/>
|
|
||||||
</a>
|
|
||||||
{this.getFileDisplay(activity)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
<FileDisplay activity={activity} scrollToBottom={scrollToBottom} />
|
||||||
case 'SEND_FILE':
|
</Notice>
|
||||||
const url = getObjectUrl(activity.encodedFile, activity.fileType)
|
)
|
||||||
return (
|
default:
|
||||||
<Notice>
|
return false
|
||||||
<div>
|
|
||||||
<T data={{
|
|
||||||
filename: <a key={0} target="_blank" href={url} rel="noopener noreferrer" download={activity.fileName}>{activity.fileName}</a>,
|
|
||||||
}} path='sentFile'/>
|
|
||||||
</div>
|
|
||||||
{this.getFileDisplay(activity)}
|
|
||||||
</Notice>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
this.getActivityComponent(this.props.activity)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,4 +138,4 @@ Activity.propTypes = {
|
|||||||
scrollToBottom: PropTypes.func.isRequired,
|
scrollToBottom: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Activity;
|
export default Activity
|
||||||
|
@ -1,100 +1,90 @@
|
|||||||
import React, { Component } from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ChatInput from 'components/Chat'
|
import ChatInput from 'components/Chat'
|
||||||
import { defer } from 'lodash'
|
|
||||||
import Activity from './Activity'
|
import Activity from './Activity'
|
||||||
|
|
||||||
import T from 'components/T'
|
import T from 'components/T'
|
||||||
|
import { defer } from 'lodash'
|
||||||
|
|
||||||
import styles from './styles.module.scss'
|
import styles from './styles.module.scss'
|
||||||
|
|
||||||
class ActivityList extends Component {
|
const ActivityList = ({ activities, openModal }) => {
|
||||||
constructor(props) {
|
const [focusChat, setFocusChat] = React.useState(false);
|
||||||
super(props)
|
const [scrolledToBottom, setScrolledToBottom] = React.useState(true);
|
||||||
|
const messageStream = React.useRef(null);
|
||||||
|
const activitiesList = React.useRef(null);
|
||||||
|
|
||||||
this.state = {
|
React.useEffect(() => {
|
||||||
zoomableImages: [],
|
const currentMessageStream = messageStream.current;
|
||||||
focusChat: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
// Update scrolledToBottom state if we scroll the activity stream
|
||||||
this.bindEvents()
|
const onScroll = () => {
|
||||||
}
|
const messageStreamHeight = messageStream.current.clientHeight
|
||||||
|
const activitiesListHeight = activitiesList.current.clientHeight
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
const bodyRect = document.body.getBoundingClientRect()
|
||||||
if (prevProps.activities.length < this.props.activities.length) {
|
const elemRect = activitiesList.current.getBoundingClientRect()
|
||||||
this.scrollToBottomIfShould()
|
const offset = elemRect.top - bodyRect.top
|
||||||
}
|
const activitiesListYPos = offset
|
||||||
}
|
|
||||||
|
|
||||||
onScroll() {
|
const newScrolledToBottom = (activitiesListHeight + (activitiesListYPos - 60)) <= messageStreamHeight
|
||||||
const messageStreamHeight = this.messageStream.clientHeight
|
if (newScrolledToBottom) {
|
||||||
const activitiesListHeight = this.activitiesList.clientHeight
|
if (!scrolledToBottom) {
|
||||||
|
setScrolledToBottom(true)
|
||||||
const bodyRect = document.body.getBoundingClientRect()
|
}
|
||||||
const elemRect = this.activitiesList.getBoundingClientRect()
|
} else if (scrolledToBottom) {
|
||||||
const offset = elemRect.top - bodyRect.top
|
setScrolledToBottom(false)
|
||||||
const activitiesListYPos = offset
|
|
||||||
|
|
||||||
const scrolledToBottom = (activitiesListHeight + (activitiesListYPos - 60)) <= messageStreamHeight
|
|
||||||
if (scrolledToBottom) {
|
|
||||||
if (!this.props.scrolledToBottom) {
|
|
||||||
this.props.setScrolledToBottom(true)
|
|
||||||
}
|
}
|
||||||
} else if (this.props.scrolledToBottom) {
|
|
||||||
this.props.setScrolledToBottom(false)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
scrollToBottomIfShould() {
|
currentMessageStream.addEventListener('scroll', onScroll)
|
||||||
if (this.props.scrolledToBottom) {
|
return () => {
|
||||||
setTimeout(() => {
|
// Unbind event if component unmounted
|
||||||
this.messageStream.scrollTop = this.messageStream.scrollHeight
|
currentMessageStream.removeEventListener('scroll', onScroll)
|
||||||
}, 0)
|
|
||||||
}
|
}
|
||||||
|
}, [scrolledToBottom])
|
||||||
|
|
||||||
|
const scrollToBottomIfShould = React.useCallback(() => {
|
||||||
|
if (scrolledToBottom) {
|
||||||
|
messageStream.current.scrollTop = messageStream.current.scrollHeight
|
||||||
|
}
|
||||||
|
}, [scrolledToBottom])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
scrollToBottomIfShould(); // Only if activities.length bigger
|
||||||
|
}, [scrollToBottomIfShould, activities]);
|
||||||
|
|
||||||
|
const scrollToBottom = React.useCallback(() => {
|
||||||
|
messageStream.current.scrollTop = messageStream.current.scrollHeight
|
||||||
|
setScrolledToBottom(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleChatClick = () => {
|
||||||
|
setFocusChat(true);
|
||||||
|
defer(() => setFocusChat(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom() {
|
return (
|
||||||
this.messageStream.scrollTop = this.messageStream.scrollHeight
|
<div className="main-chat">
|
||||||
this.props.setScrolledToBottom(true)
|
<div onClick={handleChatClick} className="message-stream h-100" ref={messageStream} data-testid="main-div">
|
||||||
}
|
<ul className="plain" ref={activitiesList}>
|
||||||
|
<li><p className={styles.tos}><button className='btn btn-link' onClick={() => openModal('About')}> <T path='agreement'/></button></p></li>
|
||||||
bindEvents() {
|
{activities.map((activity, index) => (
|
||||||
this.messageStream.addEventListener('scroll', this.onScroll.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChatClick() {
|
|
||||||
this.setState({ focusChat: true })
|
|
||||||
defer(() => this.setState({ focusChat: false }))
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="main-chat">
|
|
||||||
<div onClick={this.handleChatClick.bind(this)} className="message-stream h-100" ref={el => this.messageStream = el} data-testid="main-div">
|
|
||||||
<ul className="plain" ref={el => this.activitiesList = el}>
|
|
||||||
<li><p className={styles.tos}><button className='btn btn-link' onClick={this.props.openModal.bind(this, 'About')}> <T path='agreement'/></button></p></li>
|
|
||||||
{this.props.activities.map((activity, index) => (
|
|
||||||
<li key={index} className={`activity-item ${activity.type}`}>
|
<li key={index} className={`activity-item ${activity.type}`}>
|
||||||
<Activity activity={activity} scrollToBottom={this.scrollToBottomIfShould.bind(this)} />
|
<Activity activity={activity} scrollToBottom={scrollToBottomIfShould} />
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
<div className="chat-container">
|
|
||||||
<ChatInput scrollToBottom={this.scrollToBottom.bind(this)} focusChat={this.state.focusChat} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
<div className="chat-container">
|
||||||
}
|
<ChatInput scrollToBottom={scrollToBottom} focusChat={focusChat} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ActivityList.propTypes = {
|
ActivityList.propTypes = {
|
||||||
activities: PropTypes.array.isRequired,
|
activities: PropTypes.array.isRequired,
|
||||||
openModal: PropTypes.func.isRequired,
|
openModal: PropTypes.func.isRequired,
|
||||||
setScrolledToBottom: PropTypes.func.isRequired,
|
|
||||||
scrolledToBottom: PropTypes.bool.isRequired,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ActivityList;
|
export default ActivityList
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, fireEvent, waitFor } from '@testing-library/react';
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
import ActivityList from './ActivityList';
|
import ActivityList from './ActivityList';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import configureStore from 'store';
|
import configureStore from 'store';
|
||||||
@ -12,7 +12,7 @@ describe('ActivityList component', () => {
|
|||||||
it('should display', () => {
|
it('should display', () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ActivityList openModal={jest.fn()} activities={[]} setScrolledToBottom={jest.fn()} scrolledToBottom />
|
<ActivityList openModal={jest.fn()} activities={[]} />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ describe('ActivityList component', () => {
|
|||||||
];
|
];
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ActivityList openModal={jest.fn()} activities={activities} setScrolledToBottom={jest.fn()} scrolledToBottom />
|
<ActivityList openModal={jest.fn()} activities={activities} />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ describe('ActivityList component', () => {
|
|||||||
|
|
||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ActivityList openModal={mockOpenModal} activities={[]} setScrolledToBottom={jest.fn()} scrolledToBottom />
|
<ActivityList openModal={mockOpenModal} activities={[]} />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -59,79 +59,33 @@ describe('ActivityList component', () => {
|
|||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
|
|
||||||
expect(mockOpenModal.mock.calls[0][0]).toBe('About');
|
expect(mockOpenModal.mock.calls[0][0]).toBe('About');
|
||||||
|
jest.runAllTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should focus chat', () => {
|
it('should focus chat', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ActivityList openModal={jest.fn()} activities={[]} setScrolledToBottom={jest.fn()} scrolledToBottom />
|
<ActivityList openModal={jest.fn()} activities={[]} />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
fireEvent.click(getByTestId('main-div'));
|
fireEvent.click(getByTestId('main-div'));
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle scroll', () => {
|
it('should scroll to bottom on new message if not scrolled', () => {
|
||||||
const mockSetScrollToBottom = jest.fn();
|
jest.spyOn(Element.prototype, 'clientHeight', 'get').mockReturnValueOnce(400).mockReturnValueOnce(200);
|
||||||
jest
|
|
||||||
.spyOn(Element.prototype, 'clientHeight', 'get')
|
|
||||||
.mockReturnValueOnce(400)
|
|
||||||
.mockReturnValueOnce(200)
|
|
||||||
.mockReturnValueOnce(400)
|
|
||||||
.mockReturnValueOnce(200);
|
|
||||||
|
|
||||||
Element.prototype.getBoundingClientRect = jest
|
Element.prototype.getBoundingClientRect = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValueOnce({ top: 0 })
|
.mockReturnValueOnce({ top: 0 })
|
||||||
.mockReturnValueOnce({ top: 60 })
|
|
||||||
.mockReturnValueOnce({ top: 0 })
|
|
||||||
.mockReturnValueOnce({ top: 261 });
|
.mockReturnValueOnce({ top: 261 });
|
||||||
|
|
||||||
const { getByTestId, rerender } = render(
|
|
||||||
<Provider store={store}>
|
|
||||||
<ActivityList
|
|
||||||
openModal={jest.fn()}
|
|
||||||
activities={[]}
|
|
||||||
setScrolledToBottom={mockSetScrollToBottom}
|
|
||||||
scrolledToBottom={false}
|
|
||||||
/>
|
|
||||||
</Provider>,
|
|
||||||
);
|
|
||||||
fireEvent.scroll(getByTestId('main-div'));
|
|
||||||
|
|
||||||
expect(mockSetScrollToBottom).toHaveBeenLastCalledWith(true);
|
|
||||||
|
|
||||||
rerender(
|
|
||||||
<Provider store={store}>
|
|
||||||
<ActivityList
|
|
||||||
openModal={jest.fn()}
|
|
||||||
activities={[]}
|
|
||||||
setScrolledToBottom={mockSetScrollToBottom}
|
|
||||||
scrolledToBottom={true}
|
|
||||||
/>
|
|
||||||
</Provider>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent.scroll(getByTestId('main-div'));
|
|
||||||
|
|
||||||
expect(mockSetScrollToBottom).toHaveBeenCalledTimes(2);
|
|
||||||
expect(mockSetScrollToBottom).toHaveBeenLastCalledWith(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should scroll to bottom on new message', () => {
|
|
||||||
const mockSetScrollToBottom = jest.fn();
|
|
||||||
|
|
||||||
jest.spyOn(Element.prototype, 'scrollHeight', 'get').mockReturnValue(42);
|
jest.spyOn(Element.prototype, 'scrollHeight', 'get').mockReturnValue(42);
|
||||||
const mockScrollTop = jest.spyOn(Element.prototype, 'scrollTop', 'set');
|
const mockScrollTop = jest.spyOn(Element.prototype, 'scrollTop', 'set');
|
||||||
|
|
||||||
const { rerender } = render(
|
const { rerender, getByTestId } = render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ActivityList
|
<ActivityList openModal={jest.fn()} activities={[]} />
|
||||||
openModal={jest.fn()}
|
|
||||||
activities={[]}
|
|
||||||
setScrolledToBottom={mockSetScrollToBottom}
|
|
||||||
scrolledToBottom={true}
|
|
||||||
/>
|
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -147,14 +101,39 @@ describe('ActivityList component', () => {
|
|||||||
text: 'Hi!',
|
text: 'Hi!',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
setScrolledToBottom={mockSetScrollToBottom}
|
|
||||||
scrolledToBottom={true}
|
|
||||||
/>
|
/>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
|
|
||||||
|
expect(mockScrollTop).toHaveBeenCalledTimes(2);
|
||||||
expect(mockScrollTop).toHaveBeenLastCalledWith(42);
|
expect(mockScrollTop).toHaveBeenLastCalledWith(42);
|
||||||
|
|
||||||
|
fireEvent.scroll(getByTestId('main-div'));
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<Provider store={store}>
|
||||||
|
<ActivityList
|
||||||
|
openModal={jest.fn()}
|
||||||
|
activities={[
|
||||||
|
{
|
||||||
|
type: 'TEXT_MESSAGE',
|
||||||
|
username: 'alice',
|
||||||
|
timestamp: new Date('2020-03-14T11:01:58.135Z').valueOf(),
|
||||||
|
text: 'Hi!',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'TEXT_MESSAGE',
|
||||||
|
username: 'alice',
|
||||||
|
timestamp: new Date('2020-03-14T11:01:59.135Z').valueOf(),
|
||||||
|
text: 'Hi! every body',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Provider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockScrollTop).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -11,7 +11,6 @@ import Settings from 'components/Settings'
|
|||||||
import Welcome from 'components/Welcome'
|
import Welcome from 'components/Welcome'
|
||||||
import RoomLocked from 'components/RoomLocked'
|
import RoomLocked from 'components/RoomLocked'
|
||||||
import { X, AlertCircle } from 'react-feather'
|
import { X, AlertCircle } from 'react-feather'
|
||||||
import { defer } from 'lodash'
|
|
||||||
import Tinycon from 'tinycon'
|
import Tinycon from 'tinycon'
|
||||||
import beepFile from 'audio/beep.mp3'
|
import beepFile from 'audio/beep.mp3'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
@ -24,13 +23,6 @@ const crypto = new Crypto()
|
|||||||
Modal.setAppElement('#root');
|
Modal.setAppElement('#root');
|
||||||
|
|
||||||
class Home extends Component {
|
class Home extends Component {
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
zoomableImages: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentWillMount() {
|
async componentWillMount() {
|
||||||
const roomId = encodeURI(this.props.match.params.roomId)
|
const roomId = encodeURI(this.props.match.params.roomId)
|
||||||
@ -145,7 +137,6 @@ class Home extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
|
|
||||||
window.onfocus = () => {
|
window.onfocus = () => {
|
||||||
this.props.toggleWindowFocus(true)
|
this.props.toggleWindowFocus(true)
|
||||||
}
|
}
|
||||||
@ -175,11 +166,6 @@ class Home extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChatClick() {
|
|
||||||
this.setState({ focusChat: true })
|
|
||||||
defer(() => this.setState({ focusChat: false }))
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const modalOpts = this.getModal()
|
const modalOpts = this.getModal()
|
||||||
return (
|
return (
|
||||||
@ -201,11 +187,9 @@ class Home extends Component {
|
|||||||
translations={this.props.translations}
|
translations={this.props.translations}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ActivityList
|
<ActivityList
|
||||||
openModal={this.props.openModal}
|
openModal={this.props.openModal}
|
||||||
activities={this.props.activities}
|
activities={this.props.activities}
|
||||||
setScrolledToBottom={this.props.setScrolledToBottom}
|
|
||||||
scrolledToBottom={this.props.scrolledToBottom}
|
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={Boolean(this.props.modalComponent)}
|
isOpen={Boolean(this.props.modalComponent)}
|
||||||
@ -261,8 +245,6 @@ Home.propTypes = {
|
|||||||
modalComponent: PropTypes.string,
|
modalComponent: PropTypes.string,
|
||||||
openModal: PropTypes.func.isRequired,
|
openModal: PropTypes.func.isRequired,
|
||||||
closeModal: PropTypes.func.isRequired,
|
closeModal: PropTypes.func.isRequired,
|
||||||
setScrolledToBottom: PropTypes.func.isRequired,
|
|
||||||
scrolledToBottom: PropTypes.bool.isRequired,
|
|
||||||
iAmOwner: PropTypes.bool.isRequired,
|
iAmOwner: PropTypes.bool.isRequired,
|
||||||
userId: PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
toggleWindowFocus: PropTypes.func.isRequired,
|
toggleWindowFocus: PropTypes.func.isRequired,
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
createUser,
|
createUser,
|
||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
setScrolledToBottom,
|
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
toggleSoundEnabled,
|
toggleSoundEnabled,
|
||||||
toggleSocketConnected,
|
toggleSocketConnected,
|
||||||
@ -28,7 +27,6 @@ const mapStateToProps = (state) => {
|
|||||||
roomId: state.room.id,
|
roomId: state.room.id,
|
||||||
roomLocked: state.room.isLocked,
|
roomLocked: state.room.isLocked,
|
||||||
modalComponent: state.app.modalComponent,
|
modalComponent: state.app.modalComponent,
|
||||||
scrolledToBottom: state.app.scrolledToBottom,
|
|
||||||
iAmOwner: Boolean(me && me.isOwner),
|
iAmOwner: Boolean(me && me.isOwner),
|
||||||
faviconCount: state.app.unreadMessageCount,
|
faviconCount: state.app.unreadMessageCount,
|
||||||
soundIsEnabled: state.app.soundIsEnabled,
|
soundIsEnabled: state.app.soundIsEnabled,
|
||||||
@ -43,7 +41,6 @@ const mapDispatchToProps = {
|
|||||||
createUser,
|
createUser,
|
||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
setScrolledToBottom,
|
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
toggleSoundEnabled,
|
toggleSoundEnabled,
|
||||||
toggleSocketConnected,
|
toggleSocketConnected,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user