mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-07-18 10:49:02 +00:00
Revert "Rework Home component phase 2" (#152)
This reverts commit db6831603123dfdaf763bdf2f0fae85f044b99a9.
This commit is contained in:
parent
db68316031
commit
16be9dea83
@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, { Component } 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,128 +8,143 @@ import { getObjectUrl } from 'utils/file'
|
|||||||
|
|
||||||
import T from 'components/T'
|
import T from 'components/T'
|
||||||
|
|
||||||
const FileDisplay = ({ activity: { fileType, encodedFile, fileName, username }, scrollToBottom }) => {
|
class Activity extends Component {
|
||||||
const zoomableImage = React.useRef(null)
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
const handleImageDisplay = () => {
|
this.state = {
|
||||||
Zoom(zoomableImage.current)
|
zoomableImages: [],
|
||||||
scrollToBottom()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileType.match('image.*')) {
|
getFileDisplay(activity) {
|
||||||
return (
|
const type = activity.fileType
|
||||||
<img
|
if (type.match('image.*')) {
|
||||||
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 (
|
||||||
<Message
|
<img
|
||||||
sender={activity.username}
|
ref={c => this._zoomableImage = c}
|
||||||
message={activity.text}
|
className="image-transfer zoomable"
|
||||||
timestamp={activity.timestamp}
|
src={`data:${activity.fileType};base64,${activity.encodedFile}`}
|
||||||
|
alt={`${activity.fileName} from ${activity.username}`}
|
||||||
|
onLoad={this.handleImageDisplay.bind(this)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'USER_ENTER':
|
}
|
||||||
return (
|
return null
|
||||||
<Notice>
|
}
|
||||||
<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 (
|
|
||||||
<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} >
|
handleImageDisplay() {
|
||||||
<T data={{
|
Zoom(this._zoomableImage)
|
||||||
filename: activity.fileName,
|
this.props.scrollToBottom()
|
||||||
}} path='downloadFile'/>
|
}
|
||||||
</a>
|
|
||||||
<FileDisplay activity={activity} scrollToBottom={scrollToBottom} />
|
getActivityComponent(activity) {
|
||||||
</div>
|
switch (activity.type) {
|
||||||
)
|
case 'TEXT_MESSAGE':
|
||||||
case 'SEND_FILE':
|
return (
|
||||||
const url = getObjectUrl(activity.encodedFile, activity.fileType)
|
<Message
|
||||||
return (
|
sender={activity.username}
|
||||||
<Notice>
|
message={activity.text}
|
||||||
|
timestamp={activity.timestamp}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'USER_ENTER':
|
||||||
|
return (
|
||||||
|
<Notice>
|
||||||
|
<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 (
|
||||||
|
<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>
|
<div>
|
||||||
<T data={{
|
<T data={{
|
||||||
filename: <a key={0} target="_blank" href={url} rel="noopener noreferrer" download={activity.fileName}>{activity.fileName}</a>,
|
username: <Username key={0} username={activity.username} />,
|
||||||
}} path='sentFile'/>
|
}} path='userSentFile'/>
|
||||||
|
|
||||||
|
<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} />
|
)
|
||||||
</Notice>
|
case 'SEND_FILE':
|
||||||
)
|
const url = getObjectUrl(activity.encodedFile, activity.fileType)
|
||||||
default:
|
return (
|
||||||
return false
|
<Notice>
|
||||||
|
<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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,4 +153,4 @@ Activity.propTypes = {
|
|||||||
scrollToBottom: PropTypes.func.isRequired,
|
scrollToBottom: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Activity
|
export default Activity;
|
||||||
|
@ -1,90 +1,100 @@
|
|||||||
import React from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ChatInput from 'components/Chat'
|
import ChatInput from 'components/Chat'
|
||||||
import Activity from './Activity'
|
|
||||||
import T from 'components/T'
|
|
||||||
import { defer } from 'lodash'
|
import { defer } from 'lodash'
|
||||||
|
import Activity from './Activity'
|
||||||
|
|
||||||
|
import T from 'components/T'
|
||||||
|
|
||||||
import styles from './styles.module.scss'
|
import styles from './styles.module.scss'
|
||||||
|
|
||||||
const ActivityList = ({ activities, openModal }) => {
|
class ActivityList extends Component {
|
||||||
const [focusChat, setFocusChat] = React.useState(false);
|
constructor(props) {
|
||||||
const [scrolledToBottom, setScrolledToBottom] = React.useState(true);
|
super(props)
|
||||||
const messageStream = React.useRef(null);
|
|
||||||
const activitiesList = React.useRef(null);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
this.state = {
|
||||||
const currentMessageStream = messageStream.current;
|
zoomableImages: [],
|
||||||
|
focusChat: false,
|
||||||
// Update scrolledToBottom state if we scroll the activity stream
|
|
||||||
const onScroll = () => {
|
|
||||||
const messageStreamHeight = messageStream.current.clientHeight
|
|
||||||
const activitiesListHeight = activitiesList.current.clientHeight
|
|
||||||
|
|
||||||
const bodyRect = document.body.getBoundingClientRect()
|
|
||||||
const elemRect = activitiesList.current.getBoundingClientRect()
|
|
||||||
const offset = elemRect.top - bodyRect.top
|
|
||||||
const activitiesListYPos = offset
|
|
||||||
|
|
||||||
const newScrolledToBottom = (activitiesListHeight + (activitiesListYPos - 60)) <= messageStreamHeight
|
|
||||||
if (newScrolledToBottom) {
|
|
||||||
if (!scrolledToBottom) {
|
|
||||||
setScrolledToBottom(true)
|
|
||||||
}
|
|
||||||
} else if (scrolledToBottom) {
|
|
||||||
setScrolledToBottom(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMessageStream.addEventListener('scroll', onScroll)
|
|
||||||
return () => {
|
|
||||||
// Unbind event if component unmounted
|
|
||||||
currentMessageStream.removeEventListener('scroll', onScroll)
|
|
||||||
}
|
|
||||||
}, [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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
componentDidMount() {
|
||||||
<div className="main-chat">
|
this.bindEvents()
|
||||||
<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>
|
componentDidUpdate(prevProps) {
|
||||||
{activities.map((activity, index) => (
|
if (prevProps.activities.length < this.props.activities.length) {
|
||||||
|
this.scrollToBottomIfShould()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll() {
|
||||||
|
const messageStreamHeight = this.messageStream.clientHeight
|
||||||
|
const activitiesListHeight = this.activitiesList.clientHeight
|
||||||
|
|
||||||
|
const bodyRect = document.body.getBoundingClientRect()
|
||||||
|
const elemRect = this.activitiesList.getBoundingClientRect()
|
||||||
|
const offset = elemRect.top - bodyRect.top
|
||||||
|
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() {
|
||||||
|
if (this.props.scrolledToBottom) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.messageStream.scrollTop = this.messageStream.scrollHeight
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToBottom() {
|
||||||
|
this.messageStream.scrollTop = this.messageStream.scrollHeight
|
||||||
|
this.props.setScrolledToBottom(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
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={scrollToBottomIfShould} />
|
<Activity activity={activity} scrollToBottom={this.scrollToBottomIfShould.bind(this)} />
|
||||||
</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 } from '@testing-library/react';
|
import { render, fireEvent, waitFor } 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={[]} />
|
<ActivityList openModal={jest.fn()} activities={[]} setScrolledToBottom={jest.fn()} scrolledToBottom />
|
||||||
</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} />
|
<ActivityList openModal={jest.fn()} activities={activities} setScrolledToBottom={jest.fn()} scrolledToBottom />
|
||||||
</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={[]} />
|
<ActivityList openModal={mockOpenModal} activities={[]} setScrolledToBottom={jest.fn()} scrolledToBottom />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -59,33 +59,79 @@ 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={[]} />
|
<ActivityList openModal={jest.fn()} activities={[]} setScrolledToBottom={jest.fn()} scrolledToBottom />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
fireEvent.click(getByTestId('main-div'));
|
fireEvent.click(getByTestId('main-div'));
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should scroll to bottom on new message if not scrolled', () => {
|
it('should handle scroll', () => {
|
||||||
jest.spyOn(Element.prototype, 'clientHeight', 'get').mockReturnValueOnce(400).mockReturnValueOnce(200);
|
const mockSetScrollToBottom = jest.fn();
|
||||||
|
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, getByTestId } = render(
|
const { rerender } = render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ActivityList openModal={jest.fn()} activities={[]} />
|
<ActivityList
|
||||||
|
openModal={jest.fn()}
|
||||||
|
activities={[]}
|
||||||
|
setScrolledToBottom={mockSetScrollToBottom}
|
||||||
|
scrolledToBottom={true}
|
||||||
|
/>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -101,39 +147,14 @@ 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,6 +11,7 @@ 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'
|
||||||
@ -23,6 +24,13 @@ 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)
|
||||||
@ -137,6 +145,7 @@ class Home extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
|
|
||||||
window.onfocus = () => {
|
window.onfocus = () => {
|
||||||
this.props.toggleWindowFocus(true)
|
this.props.toggleWindowFocus(true)
|
||||||
}
|
}
|
||||||
@ -166,6 +175,11 @@ 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 (
|
||||||
@ -190,6 +204,8 @@ class Home extends Component {
|
|||||||
<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)}
|
||||||
@ -245,6 +261,8 @@ 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,6 +5,7 @@ import {
|
|||||||
createUser,
|
createUser,
|
||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
|
setScrolledToBottom,
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
toggleSoundEnabled,
|
toggleSoundEnabled,
|
||||||
toggleSocketConnected,
|
toggleSocketConnected,
|
||||||
@ -27,6 +28,7 @@ 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,
|
||||||
@ -41,6 +43,7 @@ const mapDispatchToProps = {
|
|||||||
createUser,
|
createUser,
|
||||||
openModal,
|
openModal,
|
||||||
closeModal,
|
closeModal,
|
||||||
|
setScrolledToBottom,
|
||||||
toggleWindowFocus,
|
toggleWindowFocus,
|
||||||
toggleSoundEnabled,
|
toggleSoundEnabled,
|
||||||
toggleSocketConnected,
|
toggleSocketConnected,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user