chat-store.test.js 7.0 KB

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