first commit
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { expect } from 'chai'
|
||||
import {
|
||||
fireEvent,
|
||||
screen,
|
||||
waitForElementToBeRemoved,
|
||||
} from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
import ChatPane from '../../../../../frontend/js/features/chat/components/chat-pane'
|
||||
import {
|
||||
cleanUpContext,
|
||||
renderWithEditorContext,
|
||||
} from '../../../helpers/render-with-context'
|
||||
import { stubMathJax, tearDownMathJaxStubs } from './stubs'
|
||||
|
||||
describe('<ChatPane />', function () {
|
||||
const user = {
|
||||
id: 'fake_user',
|
||||
first_name: 'fake_user_first_name',
|
||||
email: 'fake@example.com',
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-user', user)
|
||||
window.metaAttributesCache.set('ol-chatEnabled', true)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
const testMessages = [
|
||||
{
|
||||
id: 'msg_1',
|
||||
content: 'a message',
|
||||
user,
|
||||
timestamp: new Date().getTime(),
|
||||
},
|
||||
{
|
||||
id: 'msg_2',
|
||||
content: 'another message',
|
||||
user,
|
||||
timestamp: new Date().getTime(),
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
cleanUpContext()
|
||||
|
||||
stubMathJax()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
tearDownMathJaxStubs()
|
||||
})
|
||||
|
||||
it('renders multiple messages', async function () {
|
||||
fetchMock.get(/messages/, testMessages)
|
||||
|
||||
renderWithEditorContext(<ChatPane />, { user })
|
||||
|
||||
await screen.findByText('a message')
|
||||
await screen.findByText('another message')
|
||||
})
|
||||
|
||||
it('provides error message with reload button on FetchError', async function () {
|
||||
fetchMock.get(/messages/, 500)
|
||||
|
||||
renderWithEditorContext(<ChatPane />, { user })
|
||||
|
||||
// should have hit a FetchError and will prompt user to reconnect
|
||||
await screen.findByText('Try again')
|
||||
|
||||
// bring chat back up
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
fetchMock.get(/messages/, [])
|
||||
|
||||
const reconnectButton = screen.getByRole('button', {
|
||||
name: 'Try again',
|
||||
})
|
||||
expect(reconnectButton).to.exist
|
||||
|
||||
// should now reconnect with placeholder message
|
||||
fireEvent.click(reconnectButton)
|
||||
await screen.findByText('Send your first message to your collaborators')
|
||||
})
|
||||
|
||||
it('a loading spinner is rendered while the messages are loading, then disappears', async function () {
|
||||
fetchMock.get(/messages/, [], { delay: 1000 })
|
||||
|
||||
renderWithEditorContext(<ChatPane />, { user })
|
||||
|
||||
// not displayed initially
|
||||
expect(screen.queryByText('Loading…')).to.not.exist
|
||||
|
||||
// eventually displayed
|
||||
await screen.findByText('Loading…')
|
||||
|
||||
// eventually removed when the fetch call returns
|
||||
await waitForElementToBeRemoved(() => screen.getByText('Loading…'))
|
||||
})
|
||||
|
||||
describe('"send your first message" placeholder', function () {
|
||||
it('is rendered when there are no messages ', async function () {
|
||||
fetchMock.get(/messages/, [])
|
||||
|
||||
renderWithEditorContext(<ChatPane />, { user })
|
||||
|
||||
await screen.findByText('Send your first message to your collaborators')
|
||||
})
|
||||
|
||||
it('is not rendered when messages are displayed', function () {
|
||||
fetchMock.get(/messages/, testMessages)
|
||||
|
||||
renderWithEditorContext(<ChatPane />, { user })
|
||||
|
||||
expect(
|
||||
screen.queryByText('Send your first message to your collaborators')
|
||||
).to.not.exist
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
import { expect } from 'chai'
|
||||
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.getByLabelText('Send a message to your collaborators')
|
||||
})
|
||||
|
||||
it('sends a message after typing and hitting enter', function () {
|
||||
render(
|
||||
<MessageInput
|
||||
sendMessage={sendMessage}
|
||||
resetUnreadMessages={resetUnreadMessages}
|
||||
/>
|
||||
)
|
||||
|
||||
const input = screen.getByRole('textbox')
|
||||
|
||||
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(
|
||||
'Send a message to your collaborators…'
|
||||
)
|
||||
|
||||
fireEvent.click(input)
|
||||
expect(resetUnreadMessages).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,119 @@
|
||||
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 { stubMathJax, tearDownMathJaxStubs } from './stubs'
|
||||
import { UserProvider } from '@/shared/context/user-context'
|
||||
|
||||
describe('<MessageList />', function () {
|
||||
const currentUser = {
|
||||
id: 'fake_user',
|
||||
first_name: 'fake_user_first_name',
|
||||
email: 'fake@example.com',
|
||||
}
|
||||
|
||||
function createMessages() {
|
||||
return [
|
||||
{
|
||||
id: '1',
|
||||
contents: ['a message'],
|
||||
user: currentUser,
|
||||
timestamp: new Date().getTime(),
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
contents: ['another message'],
|
||||
user: currentUser,
|
||||
timestamp: new Date().getTime(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
before(function () {
|
||||
stubMathJax()
|
||||
})
|
||||
|
||||
after(function () {
|
||||
tearDownMathJaxStubs()
|
||||
})
|
||||
|
||||
let olUser
|
||||
beforeEach(function () {
|
||||
olUser = window.metaAttributesCache.get('ol-user')
|
||||
window.metaAttributesCache.set('ol-user', currentUser)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache.set('ol-user', olUser)
|
||||
})
|
||||
|
||||
it('renders multiple messages', function () {
|
||||
render(
|
||||
<UserProvider>
|
||||
<MessageList
|
||||
userId={currentUser.id}
|
||||
messages={createMessages()}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
</UserProvider>
|
||||
)
|
||||
|
||||
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).getTime()
|
||||
msgs[1].timestamp = new Date(2019, 6, 3, 4, 27).getTime()
|
||||
|
||||
render(
|
||||
<UserProvider>
|
||||
<MessageList
|
||||
userId={currentUser.id}
|
||||
messages={msgs}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
</UserProvider>
|
||||
)
|
||||
|
||||
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).getTime()
|
||||
msgs[1].timestamp = new Date(2019, 6, 3, 4, 31).getTime()
|
||||
|
||||
render(
|
||||
<UserProvider>
|
||||
<MessageList
|
||||
userId={currentUser.id}
|
||||
messages={msgs}
|
||||
resetUnreadMessages={() => {}}
|
||||
/>
|
||||
</UserProvider>
|
||||
)
|
||||
|
||||
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(
|
||||
<UserProvider>
|
||||
<MessageList
|
||||
userId={currentUser.id}
|
||||
messages={createMessages()}
|
||||
resetUnreadMessages={resetUnreadMessages}
|
||||
/>
|
||||
</UserProvider>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('list'))
|
||||
expect(resetUnreadMessages).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,105 @@
|
||||
import { expect } from 'chai'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
|
||||
import Message from '../../../../../frontend/js/features/chat/components/message'
|
||||
import { stubMathJax, tearDownMathJaxStubs } from './stubs'
|
||||
|
||||
describe('<Message />', function () {
|
||||
const currentUser = {
|
||||
id: 'fake_user',
|
||||
first_name: 'fake_user_first_name',
|
||||
email: 'fake@example.com',
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-user', currentUser)
|
||||
stubMathJax()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
tearDownMathJaxStubs()
|
||||
})
|
||||
|
||||
it('renders a basic message', function () {
|
||||
const message = {
|
||||
contents: ['a message'],
|
||||
user: currentUser,
|
||||
}
|
||||
|
||||
render(<Message message={message} fromSelf />)
|
||||
|
||||
screen.getByText('a message')
|
||||
})
|
||||
|
||||
it('renders a message with multiple contents', function () {
|
||||
const message = {
|
||||
contents: ['a message', 'another message'],
|
||||
user: currentUser,
|
||||
}
|
||||
|
||||
render(<Message message={message} fromSelf />)
|
||||
|
||||
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 message={message} fromSelf />)
|
||||
|
||||
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 message={message} fromSelf />)
|
||||
|
||||
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 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 message={msg} />)
|
||||
|
||||
expect(screen.queryByText(otherUser.first_name)).to.not.exist
|
||||
screen.getByText(msg.user.email)
|
||||
})
|
||||
})
|
||||
})
|
||||
14
services/web/test/frontend/features/chat/components/stubs.js
Normal file
14
services/web/test/frontend/features/chat/components/stubs.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import sinon from 'sinon'
|
||||
|
||||
export function stubMathJax() {
|
||||
window.MathJax = {
|
||||
Hub: {
|
||||
Queue: sinon.stub(),
|
||||
config: { tex2jax: { inlineMath: [['$', '$']] } },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function tearDownMathJaxStubs() {
|
||||
delete window.MathJax
|
||||
}
|
||||
Reference in New Issue
Block a user