chat-store.test.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import { expect } from 'chai'
  2. import sinon from 'sinon'
  3. import fetchMock from 'fetch-mock'
  4. import {
  5. ChatStore,
  6. MESSAGE_LIMIT
  7. } from '../../../../../frontend/js/features/chat/store/chat-store'
  8. describe('ChatStore', function() {
  9. let store, socket, mockSocketMessage
  10. const user = {
  11. id: '123abc'
  12. }
  13. const testProjectId = 'project-123'
  14. const testMessage = {
  15. content: 'hello',
  16. timestamp: new Date().getTime(),
  17. user
  18. }
  19. beforeEach(function() {
  20. fetchMock.reset()
  21. window.csrfToken = 'csrf_tok'
  22. socket = { on: sinon.stub(), removeListener: sinon.stub() }
  23. window._ide = { socket }
  24. mockSocketMessage = message => socket.on.getCall(0).args[1](message)
  25. store = new ChatStore(user, testProjectId)
  26. })
  27. afterEach(function() {
  28. fetchMock.restore()
  29. delete window._ide
  30. delete window.csrfToken
  31. delete window.user
  32. delete window.project_id
  33. })
  34. describe('new message events', function() {
  35. it('subscribes to the socket for new message events', function() {
  36. expect(socket.on).to.be.calledWith('new-chat-message')
  37. })
  38. it('notifies an update event after new messages are received', function() {
  39. const subscriber = sinon.stub()
  40. store.on('updated', subscriber)
  41. mockSocketMessage(testMessage)
  42. expect(subscriber).to.be.calledOnce
  43. })
  44. it('can unsubscribe from events', function() {
  45. const subscriber = sinon.stub()
  46. store.on('updated', subscriber)
  47. store.off('updated', subscriber)
  48. mockSocketMessage(testMessage)
  49. expect(subscriber).not.to.be.called
  50. })
  51. it('when the message is from other user, it is added to the messages list', function() {
  52. mockSocketMessage({ ...testMessage, id: 'other_user' })
  53. expect(store.messages[store.messages.length - 1]).to.deep.equal({
  54. user: testMessage.user,
  55. timestamp: testMessage.timestamp,
  56. contents: [testMessage.content]
  57. })
  58. })
  59. describe('messages sent by the user', function() {
  60. beforeEach(function() {
  61. fetchMock.post(/messages/, 204)
  62. })
  63. it('are not added to the message list', async function() {
  64. await store.sendMessage(testMessage.content)
  65. const originalMessageList = store.messages.slice(0)
  66. mockSocketMessage(testMessage)
  67. expect(originalMessageList).to.deep.equal(store.messages)
  68. // next message by a different user is added normally
  69. const otherMessage = {
  70. ...testMessage,
  71. user: { id: 'other_user' },
  72. content: 'other'
  73. }
  74. mockSocketMessage(otherMessage)
  75. expect(store.messages.length).to.equal(originalMessageList.length + 1)
  76. expect(store.messages[store.messages.length - 1]).to.deep.equal({
  77. user: otherMessage.user,
  78. timestamp: otherMessage.timestamp,
  79. contents: [otherMessage.content]
  80. })
  81. })
  82. it("don't notify an update event after new messages are received", async function() {
  83. await store.sendMessage(testMessage.content)
  84. const subscriber = sinon.stub()
  85. store.on('updated', subscriber)
  86. mockSocketMessage(testMessage)
  87. expect(subscriber).not.to.be.called
  88. })
  89. })
  90. })
  91. describe('loadMoreMessages()', function() {
  92. it('aborts the request when the entire message list is loaded', async function() {
  93. store.atEnd = true
  94. await store.loadMoreMessages()
  95. expect(fetchMock.calls().length).to.equal(0)
  96. expect(store.loading).to.equal(false)
  97. })
  98. it('updates the list of messages', async function() {
  99. const originalMessageList = store.messages.slice(0)
  100. fetchMock.get(/messages/, [testMessage])
  101. await store.loadMoreMessages()
  102. expect(store.messages.length).to.equal(originalMessageList.length + 1)
  103. expect(store.messages[store.messages.length - 1]).to.deep.equal({
  104. user: testMessage.user,
  105. timestamp: testMessage.timestamp,
  106. contents: [testMessage.content]
  107. })
  108. })
  109. it('notifies an update event for when the loading starts, and a second one once data is available', async function() {
  110. const subscriber = sinon.stub()
  111. store.on('updated', subscriber)
  112. fetchMock.get(/messages/, [testMessage])
  113. await store.loadMoreMessages()
  114. expect(subscriber).to.be.calledTwice
  115. })
  116. it('marks `atEnd` flag to true when there are no more messages to retrieve', async function() {
  117. expect(store.atEnd).to.equal(false)
  118. fetchMock.get(/messages/, [testMessage])
  119. await store.loadMoreMessages()
  120. expect(store.atEnd).to.equal(true)
  121. })
  122. it('marks `atEnd` flag to false when there are still messages to retrieve', async function() {
  123. const messages = []
  124. for (let i = 0; i < MESSAGE_LIMIT; i++) {
  125. messages.push({ ...testMessage, content: `message #${i}` })
  126. }
  127. expect(store.atEnd).to.equal(false)
  128. fetchMock.get(/messages/, messages)
  129. await store.loadMoreMessages()
  130. expect(store.atEnd).to.equal(false)
  131. })
  132. it('subsequent requests for new messages start at the timestamp of the latest message', async function() {
  133. const messages = []
  134. for (let i = 0; i < MESSAGE_LIMIT - 1; i++) {
  135. // sending enough messages so it doesn't mark `atEnd === true`
  136. messages.push({ ...testMessage, content: `message #${i}` })
  137. }
  138. const timestamp = new Date().getTime()
  139. messages.push({ ...testMessage, timestamp })
  140. fetchMock.get(/messages/, messages)
  141. await store.loadMoreMessages()
  142. fetchMock.get(/messages/, [])
  143. await store.loadMoreMessages()
  144. expect(fetchMock.calls().length).to.equal(2)
  145. const url = fetchMock.lastCall()[0]
  146. expect(url).to.match(new RegExp(`&before=${timestamp}`))
  147. })
  148. })
  149. describe('sendMessage()', function() {
  150. beforeEach(function() {
  151. fetchMock.post(/messages/, 204)
  152. })
  153. it('appends the message to the list', async function() {
  154. const originalMessageList = store.messages.slice(0)
  155. await store.sendMessage('a message')
  156. expect(store.messages.length).to.equal(originalMessageList.length + 1)
  157. const lastMessage = store.messages[store.messages.length - 1]
  158. expect(lastMessage.contents).to.deep.equal(['a message'])
  159. expect(lastMessage.user).to.deep.equal(user)
  160. expect(lastMessage.timestamp).to.be.greaterThan(0)
  161. })
  162. it('notifies an update event', async function() {
  163. const subscriber = sinon.stub()
  164. store.on('updated', subscriber)
  165. await store.sendMessage('a message')
  166. expect(subscriber).to.be.calledOnce
  167. })
  168. it('sends an http POST request to the server', async function() {
  169. await store.sendMessage('a message')
  170. expect(fetchMock.calls().length).to.equal(1)
  171. const body = fetchMock.lastCall()[1].body
  172. expect(JSON.parse(body)).to.deep.equal({
  173. content: 'a message',
  174. _csrf: 'csrf_tok'
  175. })
  176. })
  177. it('ignores empty messages', async function() {
  178. const subscriber = sinon.stub()
  179. store.on('updated', subscriber)
  180. await store.sendMessage('')
  181. await store.sendMessage(null)
  182. expect(subscriber).not.to.be.called
  183. })
  184. })
  185. describe('destroy', function() {
  186. beforeEach(function() {
  187. fetchMock.post(/messages/, 204)
  188. })
  189. it('removes event listeners', async function() {
  190. const subscriber = sinon.stub()
  191. store.on('updated', subscriber)
  192. store.destroy()
  193. await store.sendMessage('a message')
  194. expect(subscriber).not.to.be.called
  195. })
  196. })
  197. })