| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- import { expect } from 'chai'
- import sinon from 'sinon'
- import fetchMock from 'fetch-mock'
- import {
- ChatStore,
- MESSAGE_LIMIT
- } from '../../../../../frontend/js/features/chat/store/chat-store'
- describe('ChatStore', function() {
- let store, socket, mockSocketMessage
- const user = {
- id: '123abc'
- }
- const testProjectId = 'project-123'
- const testMessage = {
- id: 'msg_1',
- content: 'hello',
- timestamp: new Date().getTime(),
- user
- }
- beforeEach(function() {
- fetchMock.reset()
- window.csrfToken = 'csrf_tok'
- socket = { on: sinon.stub(), removeListener: sinon.stub() }
- window._ide = { socket }
- mockSocketMessage = message => socket.on.getCall(0).args[1](message)
- store = new ChatStore(user, testProjectId)
- })
- afterEach(function() {
- fetchMock.restore()
- delete window._ide
- delete window.csrfToken
- delete window.user
- delete window.project_id
- })
- describe('new message events', function() {
- it('subscribes to the socket for new message events', function() {
- expect(socket.on).to.be.calledWith('new-chat-message')
- })
- it('notifies an update event after new messages are received', function() {
- const subscriber = sinon.stub()
- store.on('updated', subscriber)
- mockSocketMessage(testMessage)
- expect(subscriber).to.be.calledOnce
- })
- it('can unsubscribe from events', function() {
- const subscriber = sinon.stub()
- store.on('updated', subscriber)
- store.off('updated', subscriber)
- mockSocketMessage(testMessage)
- expect(subscriber).not.to.be.called
- })
- it('when the message is from other user, it is added to the messages list', function() {
- mockSocketMessage({ ...testMessage, id: 'other_user_msg' })
- expect(store.messages[store.messages.length - 1]).to.deep.equal({
- id: 'other_user_msg',
- user: testMessage.user,
- timestamp: testMessage.timestamp,
- contents: [testMessage.content]
- })
- })
- describe('messages sent by the user', function() {
- beforeEach(function() {
- fetchMock.post(/messages/, 204)
- })
- it('are not added to the message list', async function() {
- await store.sendMessage(testMessage.content)
- const originalMessageList = store.messages.slice(0)
- mockSocketMessage(testMessage)
- expect(originalMessageList).to.deep.equal(store.messages)
- // next message by a different user is added normally
- const otherMessage = {
- ...testMessage,
- id: 'other_user_msg',
- user: { id: 'other_user' },
- content: 'other'
- }
- mockSocketMessage(otherMessage)
- expect(store.messages.length).to.equal(originalMessageList.length + 1)
- expect(store.messages[store.messages.length - 1]).to.deep.equal({
- id: otherMessage.id,
- user: otherMessage.user,
- timestamp: otherMessage.timestamp,
- contents: [otherMessage.content]
- })
- })
- it("don't notify an update event after new messages are received", async function() {
- await store.sendMessage(testMessage.content)
- const subscriber = sinon.stub()
- store.on('updated', subscriber)
- mockSocketMessage(testMessage)
- expect(subscriber).not.to.be.called
- })
- it("have an 'id' property", async function() {
- await store.sendMessage(testMessage.content)
- expect(typeof store.messages[0].id).to.equal('string')
- })
- })
- })
- describe('loadMoreMessages()', function() {
- it('aborts the request when the entire message list is loaded', async function() {
- store.atEnd = true
- await store.loadMoreMessages()
- expect(fetchMock.calls().length).to.equal(0)
- expect(store.loading).to.equal(false)
- })
- it('updates the list of messages', async function() {
- const originalMessageList = store.messages.slice(0)
- fetchMock.get(/messages/, [testMessage])
- await store.loadMoreMessages()
- expect(store.messages.length).to.equal(originalMessageList.length + 1)
- expect(store.messages[store.messages.length - 1]).to.deep.equal({
- id: testMessage.id,
- user: testMessage.user,
- timestamp: testMessage.timestamp,
- contents: [testMessage.content]
- })
- })
- it('notifies an update event for when the loading starts, and a second one once data is available', async function() {
- const subscriber = sinon.stub()
- store.on('updated', subscriber)
- fetchMock.get(/messages/, [testMessage])
- await store.loadMoreMessages()
- expect(subscriber).to.be.calledTwice
- })
- it('marks `atEnd` flag to true when there are no more messages to retrieve', async function() {
- expect(store.atEnd).to.equal(false)
- fetchMock.get(/messages/, [testMessage])
- await store.loadMoreMessages()
- expect(store.atEnd).to.equal(true)
- })
- it('marks `atEnd` flag to false when there are still messages to retrieve', async function() {
- const messages = []
- for (let i = 0; i < MESSAGE_LIMIT; i++) {
- messages.push({ ...testMessage, content: `message #${i}` })
- }
- expect(store.atEnd).to.equal(false)
- fetchMock.get(/messages/, messages)
- await store.loadMoreMessages()
- expect(store.atEnd).to.equal(false)
- })
- it('subsequent requests for new messages start at the timestamp of the latest message', async function() {
- const messages = []
- for (let i = 0; i < MESSAGE_LIMIT - 1; i++) {
- // sending enough messages so it doesn't mark `atEnd === true`
- messages.push({ ...testMessage, content: `message #${i}` })
- }
- const timestamp = new Date().getTime()
- messages.push({ ...testMessage, timestamp })
- fetchMock.get(/messages/, messages)
- await store.loadMoreMessages()
- fetchMock.get(/messages/, [])
- await store.loadMoreMessages()
- expect(fetchMock.calls().length).to.equal(2)
- const url = fetchMock.lastCall()[0]
- expect(url).to.match(new RegExp(`&before=${timestamp}`))
- })
- })
- describe('sendMessage()', function() {
- beforeEach(function() {
- fetchMock.post(/messages/, 204)
- })
- it('appends the message to the list', async function() {
- const originalMessageList = store.messages.slice(0)
- await store.sendMessage('a message')
- expect(store.messages.length).to.equal(originalMessageList.length + 1)
- const lastMessage = store.messages[store.messages.length - 1]
- expect(lastMessage.contents).to.deep.equal(['a message'])
- expect(lastMessage.user).to.deep.equal(user)
- expect(lastMessage.timestamp).to.be.greaterThan(0)
- })
- it('notifies an update event', async function() {
- const subscriber = sinon.stub()
- store.on('updated', subscriber)
- await store.sendMessage('a message')
- expect(subscriber).to.be.calledOnce
- })
- it('sends an http POST request to the server', async function() {
- await store.sendMessage('a message')
- expect(fetchMock.calls().length).to.equal(1)
- const body = fetchMock.lastCall()[1].body
- expect(JSON.parse(body)).to.deep.equal({
- content: 'a message',
- _csrf: 'csrf_tok'
- })
- })
- it('ignores empty messages', async function() {
- const subscriber = sinon.stub()
- store.on('updated', subscriber)
- await store.sendMessage('')
- await store.sendMessage(null)
- expect(subscriber).not.to.be.called
- })
- })
- describe('destroy', function() {
- beforeEach(function() {
- fetchMock.post(/messages/, 204)
- })
- it('removes event listeners', async function() {
- const subscriber = sinon.stub()
- store.on('updated', subscriber)
- store.destroy()
- await store.sendMessage('a message')
- expect(subscriber).not.to.be.called
- })
- })
- })
|