mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-10 20:27:13 +00:00
Merge pull request #3267 from overleaf/msm-react-chat-tests
React chat tests GitOrigin-RevId: e3b4d5b7cb2657d9aad7e1006c18db4e6c0d8a3f
This commit is contained in:
parent
c504f2a64c
commit
df37668180
10 changed files with 457 additions and 5 deletions
|
@ -58,7 +58,7 @@ function LoadingSpinner() {
|
|||
return (
|
||||
<div className="loading">
|
||||
<Icon type="fw" modifier="refresh" spin />
|
||||
{` ${t('loading')}...`}
|
||||
{` ${t('loading')}…`}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ function MessageContent({ content }) {
|
|||
useEffect(
|
||||
() => {
|
||||
// adds attributes to all the links generated by <Linkify/>, required due to https://github.com/tasti/react-linkify/issues/99
|
||||
root.current.getElementsByTagName('a').forEach(a => {
|
||||
for (let a of root.current.getElementsByTagName('a')) {
|
||||
a.setAttribute('target', '_blank')
|
||||
a.setAttribute('rel', 'noreferrer noopener')
|
||||
})
|
||||
}
|
||||
|
||||
// MathJax typesetting
|
||||
const MJHub = window.MathJax.Hub
|
||||
|
|
|
@ -16,7 +16,7 @@ function MessageInput({ resetUnreadMessages, sendMessage }) {
|
|||
return (
|
||||
<div className="new-message">
|
||||
<textarea
|
||||
placeholder={`${t('your_message')}...`}
|
||||
placeholder={`${t('your_message')}…`}
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={resetUnreadMessages}
|
||||
/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"test:unit:run_dir": "mocha --recursive --timeout 25000 --exit --grep=$MOCHA_GREP --file test/unit/bootstrap.js",
|
||||
"test:unit:app": "npm run test:unit:run_dir -- test/unit/src",
|
||||
"test:unit:app:parallel": "parallel --plain --keep-order --halt now,fail=1 npm run test:unit:run_dir -- {} ::: test/unit/src/*",
|
||||
"test:frontend": "NODE_ENV=test mocha --recursive --exit --grep=$MOCHA_GREP --require test/frontend/bootstrap.js test/frontend modules/*/test/frontend",
|
||||
"test:frontend": "NODE_ENV=test TZ=GMT mocha --recursive --exit --grep=$MOCHA_GREP --require test/frontend/bootstrap.js test/frontend modules/*/test/frontend",
|
||||
"test:frontend:coverage": "c8 --all --include 'frontend/js' --include 'modules/*/frontend/js' --exclude 'frontend/js/vendor' --reporter=lcov --reporter=text-summary npm run test:frontend",
|
||||
"test:karma": "karma start",
|
||||
"test:karma:single": "karma start --single-run",
|
||||
|
|
12
services/web/test/frontend/bootstrap.js
vendored
12
services/web/test/frontend/bootstrap.js
vendored
|
@ -11,3 +11,15 @@ chai.use(require('sinon-chai'))
|
|||
|
||||
window.i18n = { currentLangCode: 'en' }
|
||||
require('../../frontend/js/i18n')
|
||||
|
||||
const moment = require('moment')
|
||||
moment.updateLocale('en', {
|
||||
calendar: {
|
||||
lastDay: '[Yesterday]',
|
||||
sameDay: '[Today]',
|
||||
nextDay: '[Tomorrow]',
|
||||
lastWeek: 'ddd, Do MMM YY',
|
||||
nextWeek: 'ddd, Do MMM YY',
|
||||
sameElse: 'ddd, Do MMM YY'
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
import React from 'react'
|
||||
import { expect } from 'chai'
|
||||
import { screen, render } from '@testing-library/react'
|
||||
|
||||
import ChatPane from '../../../../../frontend/js/features/chat/components/chat-pane'
|
||||
import {
|
||||
stubGlobalUser,
|
||||
stubMathJax,
|
||||
stubUIConfig,
|
||||
tearDownGlobalUserStub,
|
||||
tearDownMathJaxStubs,
|
||||
tearDownUIConfigStubs
|
||||
} from './stubs'
|
||||
|
||||
describe('<ChatPane />', function() {
|
||||
const currentUser = {
|
||||
id: 'fake_user',
|
||||
first_name: 'fake_user_first_name',
|
||||
email: 'fake@example.com'
|
||||
}
|
||||
|
||||
function createMessages() {
|
||||
return [
|
||||
{
|
||||
contents: ['a message'],
|
||||
user: currentUser,
|
||||
timestamp: new Date()
|
||||
},
|
||||
{
|
||||
contents: ['another message'],
|
||||
user: currentUser,
|
||||
timestamp: new Date()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
before(function() {
|
||||
stubGlobalUser(currentUser) // required by ColorManager
|
||||
stubUIConfig()
|
||||
stubMathJax()
|
||||
})
|
||||
|
||||
after(function() {
|
||||
tearDownGlobalUserStub()
|
||||
tearDownUIConfigStubs()
|
||||
tearDownMathJaxStubs()
|
||||
})
|
||||
|
||||
it('renders multiple messages', function() {
|
||||
render(
|
||||
<ChatPane
|
||||
loadMoreMessages={() => {}}
|
||||
sendMessage={() => {}}
|
||||
userId={currentUser.id}
|
||||
messages={createMessages()}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.getByText('a message')
|
||||
screen.getByText('another message')
|
||||
})
|
||||
|
||||
describe('loading spinner', function() {
|
||||
it('is rendered while the messages is loading', function() {
|
||||
render(
|
||||
<ChatPane
|
||||
loading
|
||||
loadMoreMessages={() => {}}
|
||||
sendMessage={() => {}}
|
||||
userId={currentUser.id}
|
||||
messages={createMessages()}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
)
|
||||
screen.getByText('Loading…')
|
||||
})
|
||||
|
||||
it('is not rendered when the messages are not loading', function() {
|
||||
render(
|
||||
<ChatPane
|
||||
loading={false}
|
||||
loadMoreMessages={() => {}}
|
||||
sendMessage={() => {}}
|
||||
userId={currentUser.id}
|
||||
messages={createMessages()}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
expect(screen.queryByText('Loading…')).to.not.exist
|
||||
})
|
||||
|
||||
describe('"send your first message" placeholder', function() {
|
||||
it('is rendered when there are no messages ', function() {
|
||||
render(
|
||||
<ChatPane
|
||||
loadMoreMessages={() => {}}
|
||||
sendMessage={() => {}}
|
||||
userId={currentUser.id}
|
||||
messages={[]}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
)
|
||||
screen.getByText('Send your first message to your collaborators')
|
||||
})
|
||||
|
||||
it('is not rendered when there are some messages', function() {
|
||||
render(
|
||||
<ChatPane
|
||||
loading={false}
|
||||
loadMoreMessages={() => {}}
|
||||
sendMessage={() => {}}
|
||||
userId={currentUser.id}
|
||||
messages={createMessages()}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
expect(screen.queryByText('Send your first message to your collaborators'))
|
||||
.to.not.exist
|
||||
})
|
||||
})
|
|
@ -0,0 +1,56 @@
|
|||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { screen, render, fireEvent } from '@testing-library/react'
|
||||
|
||||
import MessageInput from '../../../../../frontend/js/features/chat/components/message-input'
|
||||
|
||||
describe('<MessageInput />', function() {
|
||||
let resetUnreadMessages, sendMessage
|
||||
|
||||
beforeEach(function() {
|
||||
resetUnreadMessages = sinon.stub()
|
||||
sendMessage = sinon.stub()
|
||||
})
|
||||
|
||||
it('renders successfully', function() {
|
||||
render(
|
||||
<MessageInput
|
||||
sendMessage={sendMessage}
|
||||
resetUnreadMessages={resetUnreadMessages}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.getByPlaceholderText('Your Message…')
|
||||
})
|
||||
|
||||
it('sends a message after typing and hitting enter', function() {
|
||||
render(
|
||||
<MessageInput
|
||||
sendMessage={sendMessage}
|
||||
resetUnreadMessages={resetUnreadMessages}
|
||||
/>
|
||||
)
|
||||
|
||||
const input = screen.getByPlaceholderText('Your Message…')
|
||||
|
||||
fireEvent.change(input, { target: { value: 'hello world' } })
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
expect(sendMessage).to.be.calledOnce
|
||||
expect(sendMessage).to.be.calledWith('hello world')
|
||||
})
|
||||
|
||||
it('resets the number of unread messages after clicking on the input', function() {
|
||||
render(
|
||||
<MessageInput
|
||||
sendMessage={sendMessage}
|
||||
resetUnreadMessages={resetUnreadMessages}
|
||||
/>
|
||||
)
|
||||
|
||||
const input = screen.getByPlaceholderText('Your Message…')
|
||||
|
||||
fireEvent.click(input)
|
||||
expect(resetUnreadMessages).to.be.calledOnce
|
||||
})
|
||||
})
|
|
@ -0,0 +1,110 @@
|
|||
import React from 'react'
|
||||
import sinon from 'sinon'
|
||||
import { expect } from 'chai'
|
||||
import { screen, render, fireEvent } from '@testing-library/react'
|
||||
|
||||
import MessageList from '../../../../../frontend/js/features/chat/components/message-list'
|
||||
import {
|
||||
stubGlobalUser,
|
||||
stubMathJax,
|
||||
stubUIConfig,
|
||||
tearDownGlobalUserStub,
|
||||
tearDownMathJaxStubs,
|
||||
tearDownUIConfigStubs
|
||||
} from './stubs'
|
||||
|
||||
describe('<MessageList />', function() {
|
||||
const currentUser = {
|
||||
id: 'fake_user',
|
||||
first_name: 'fake_user_first_name',
|
||||
email: 'fake@example.com'
|
||||
}
|
||||
|
||||
function createMessages() {
|
||||
return [
|
||||
{
|
||||
contents: ['a message'],
|
||||
user: currentUser,
|
||||
timestamp: new Date()
|
||||
},
|
||||
{
|
||||
contents: ['another message'],
|
||||
user: currentUser,
|
||||
timestamp: new Date()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
before(function() {
|
||||
stubGlobalUser(currentUser) // required by ColorManager
|
||||
stubUIConfig()
|
||||
stubMathJax()
|
||||
})
|
||||
|
||||
after(function() {
|
||||
tearDownGlobalUserStub()
|
||||
tearDownUIConfigStubs()
|
||||
tearDownMathJaxStubs()
|
||||
})
|
||||
|
||||
it('renders multiple messages', function() {
|
||||
render(
|
||||
<MessageList
|
||||
userId={currentUser.id}
|
||||
messages={createMessages()}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.getByText('a message')
|
||||
screen.getByText('another message')
|
||||
})
|
||||
|
||||
it('renders a single timestamp for all messages within 5 minutes', function() {
|
||||
const msgs = createMessages()
|
||||
msgs[0].timestamp = new Date(2019, 6, 3, 4, 23)
|
||||
msgs[1].timestamp = new Date(2019, 6, 3, 4, 27)
|
||||
|
||||
render(
|
||||
<MessageList
|
||||
userId={currentUser.id}
|
||||
messages={msgs}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.getByText('4:23 am Wed, 3rd Jul 19')
|
||||
expect(screen.queryByText('4:27 am Wed, 3rd Jul 19')).to.not.exist
|
||||
})
|
||||
|
||||
it('renders a timestamp for each messages separated for more than 5 minutes', function() {
|
||||
const msgs = createMessages()
|
||||
msgs[0].timestamp = new Date(2019, 6, 3, 4, 23)
|
||||
msgs[1].timestamp = new Date(2019, 6, 3, 4, 31)
|
||||
|
||||
render(
|
||||
<MessageList
|
||||
userId={currentUser.id}
|
||||
messages={msgs}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
screen.getByText('4:23 am Wed, 3rd Jul 19')
|
||||
screen.getByText('4:31 am Wed, 3rd Jul 19')
|
||||
})
|
||||
|
||||
it('resets the number of unread messages after clicking on the input', function() {
|
||||
const resetUnreadMessages = sinon.stub()
|
||||
render(
|
||||
<MessageList
|
||||
userId={currentUser.id}
|
||||
messages={createMessages()}
|
||||
resetUnreadMessages={resetUnreadMessages}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('list'))
|
||||
expect(resetUnreadMessages).to.be.calledOnce
|
||||
})
|
||||
})
|
|
@ -0,0 +1,116 @@
|
|||
import { expect } from 'chai'
|
||||
import React from 'react'
|
||||
import { screen, render } from '@testing-library/react'
|
||||
|
||||
import Message from '../../../../../frontend/js/features/chat/components/message'
|
||||
import {
|
||||
stubGlobalUser,
|
||||
stubMathJax,
|
||||
stubUIConfig,
|
||||
tearDownGlobalUserStub,
|
||||
tearDownMathJaxStubs,
|
||||
tearDownUIConfigStubs
|
||||
} from './stubs'
|
||||
|
||||
describe('<Message />', function() {
|
||||
const currentUser = {
|
||||
id: 'fake_user',
|
||||
first_name: 'fake_user_first_name',
|
||||
email: 'fake@example.com'
|
||||
}
|
||||
|
||||
before(function() {
|
||||
stubGlobalUser(currentUser) // required by ColorManager
|
||||
stubUIConfig()
|
||||
stubMathJax()
|
||||
})
|
||||
|
||||
after(function() {
|
||||
tearDownGlobalUserStub()
|
||||
tearDownUIConfigStubs()
|
||||
tearDownMathJaxStubs()
|
||||
})
|
||||
|
||||
it('renders a basic message', function() {
|
||||
const message = {
|
||||
contents: ['a message'],
|
||||
user: currentUser
|
||||
}
|
||||
|
||||
render(<Message userId={currentUser.id} message={message} />)
|
||||
|
||||
screen.getByText('a message')
|
||||
})
|
||||
|
||||
it('renders a message with multiple contents', function() {
|
||||
const message = {
|
||||
contents: ['a message', 'another message'],
|
||||
user: currentUser
|
||||
}
|
||||
|
||||
render(<Message userId={currentUser.id} message={message} />)
|
||||
|
||||
screen.getByText('a message')
|
||||
screen.getByText('another message')
|
||||
})
|
||||
|
||||
it('renders HTML links within messages', function() {
|
||||
const message = {
|
||||
contents: [
|
||||
'a message with a <a href="https://overleaf.com">link to Overleaf</a>'
|
||||
],
|
||||
user: currentUser
|
||||
}
|
||||
|
||||
render(<Message userId={currentUser.id} message={message} />)
|
||||
|
||||
screen.getByRole('link', { name: 'https://overleaf.com' })
|
||||
})
|
||||
|
||||
describe('when the message is from the user themselves', function() {
|
||||
const message = {
|
||||
contents: ['a message'],
|
||||
user: currentUser
|
||||
}
|
||||
|
||||
it('does not render the user name nor the email', function() {
|
||||
render(<Message userId={currentUser.id} message={message} />)
|
||||
|
||||
expect(screen.queryByText(currentUser.first_name)).to.not.exist
|
||||
expect(screen.queryByText(currentUser.email)).to.not.exist
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the message is from other user', function() {
|
||||
const otherUser = {
|
||||
id: 'other_user',
|
||||
first_name: 'other_user_first_name'
|
||||
}
|
||||
|
||||
const message = {
|
||||
contents: ['a message'],
|
||||
user: otherUser
|
||||
}
|
||||
|
||||
it('should render the other user name', function() {
|
||||
render(<Message userId={currentUser.id} message={message} />)
|
||||
|
||||
screen.getByText(otherUser.first_name)
|
||||
})
|
||||
|
||||
it('should render the other user email when their name is not available', function() {
|
||||
const msg = {
|
||||
contents: message.contents,
|
||||
user: {
|
||||
id: otherUser.id,
|
||||
email: 'other@example.com'
|
||||
}
|
||||
}
|
||||
|
||||
render(<Message userId={currentUser.id} message={msg} />)
|
||||
|
||||
expect(screen.queryByText(otherUser.first_name)).to.not.exist
|
||||
screen.getByText(msg.user.email)
|
||||
})
|
||||
})
|
||||
})
|
35
services/web/test/frontend/features/chat/components/stubs.js
Normal file
35
services/web/test/frontend/features/chat/components/stubs.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import sinon from 'sinon'
|
||||
|
||||
export function stubUIConfig() {
|
||||
window.uiConfig = {
|
||||
chatMessageBorderSaturation: '85%',
|
||||
chatMessageBorderLightness: '40%',
|
||||
chatMessageBgSaturation: '85%',
|
||||
chatMessageBgLightness: '40%'
|
||||
}
|
||||
}
|
||||
|
||||
export function tearDownUIConfigStubs() {
|
||||
delete window.uiConfig
|
||||
}
|
||||
|
||||
export function stubMathJax() {
|
||||
window.MathJax = {
|
||||
Hub: {
|
||||
Queue: sinon.stub(),
|
||||
config: { tex2jax: { inlineMath: [['$', '$']] } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function tearDownMathJaxStubs() {
|
||||
delete window.MathJax
|
||||
}
|
||||
|
||||
export function stubGlobalUser(user) {
|
||||
window.user = user
|
||||
}
|
||||
|
||||
export function tearDownGlobalUserStub() {
|
||||
delete window.user
|
||||
}
|
Loading…
Add table
Reference in a new issue