unreadStore.test.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import { afterEach, beforeEach, expect, it, vi } from 'vitest'
  2. import { flushPromises } from '@vue/test-utils'
  3. import { apiRequest } from '@/api/client'
  4. import { useUnreadStore } from '@/stores/unread'
  5. import { emptyUnread, type UnreadSnapshot } from '@shared/unread'
  6. vi.mock('@/api/client', () => ({ apiRequest: vi.fn() }))
  7. const unread = useUnreadStore()
  8. function snapshot(ids: string[]): UnreadSnapshot {
  9. return { total: ids.length, bot: ids.length, chat: 0, conversations: ids.length ? [{
  10. mode: 'bot', id: 'c', name: '研发助手', preview: '回复', count: ids.length, updatedAt: 1,
  11. messages: ids.map((id, seq) => ({ id, seq, version: 1 })),
  12. }] : [] }
  13. }
  14. function deferred<T>() {
  15. let resolve!: (value: T) => void
  16. const promise = new Promise<T>(done => { resolve = done })
  17. return { promise, resolve }
  18. }
  19. beforeEach(() => { vi.useFakeTimers(); unread.reset(); vi.mocked(apiRequest).mockReset() })
  20. afterEach(() => { unread.reset(); vi.restoreAllMocks(); vi.useRealTimers() })
  21. it('updates the badge from the read acknowledgement without waiting for another GET', async () => {
  22. const initial = snapshot(['first', 'second'])
  23. vi.mocked(apiRequest).mockResolvedValueOnce(initial)
  24. unread.start('owner'); await flushPromises()
  25. vi.mocked(apiRequest).mockImplementation(async path => {
  26. if (path === '/api/app/unread/read') return snapshot(['second']) as never
  27. throw new Error('后续同步暂不可用')
  28. })
  29. await unread.visible('bot', 'c', undefined, ['first'])
  30. expect(unread.total).toBe(1)
  31. expect(unread.snapshot.conversations[0]?.messages.map(message => message.id)).toEqual(['second'])
  32. expect(apiRequest).toHaveBeenLastCalledWith('/api/app/unread/read', expect.objectContaining({
  33. body: { items: [{ mode: 'bot', id: 'c', messages: [{ id: 'first', seq: 0, version: 1 }] }] },
  34. }))
  35. await unread.visible('bot', 'c', undefined, ['first'])
  36. expect(apiRequest).toHaveBeenCalledTimes(2)
  37. })
  38. it('does not let a poll started before reading restore already-read messages', async () => {
  39. const initial = snapshot(['first', 'second']), poll = deferred<UnreadSnapshot>()
  40. vi.mocked(apiRequest).mockResolvedValueOnce(initial)
  41. unread.start('owner'); await flushPromises()
  42. vi.mocked(apiRequest).mockImplementation(path => path === '/api/app/unread'
  43. ? poll.promise as never : Promise.resolve(snapshot(['second'])) as never)
  44. const polling = unread.refresh()
  45. await unread.visible('bot', 'c', undefined, ['first'])
  46. poll.resolve(initial); await polling
  47. expect(unread.total).toBe(1)
  48. })
  49. it('reconciles concurrent read acknowledgements that arrive in reverse order', async () => {
  50. const initial = snapshot(['first', 'second'])
  51. const first = deferred<UnreadSnapshot>(), second = deferred<UnreadSnapshot>()
  52. vi.mocked(apiRequest).mockResolvedValueOnce(initial)
  53. unread.start('owner'); await flushPromises()
  54. vi.mocked(apiRequest).mockReturnValueOnce(first.promise as never).mockReturnValueOnce(second.promise as never).mockResolvedValue(emptyUnread())
  55. const readingFirst = unread.visible('bot', 'c', undefined, ['first'])
  56. const readingSecond = unread.visible('bot', 'c', undefined, ['second'])
  57. second.resolve(emptyUnread()); await readingSecond
  58. first.resolve(snapshot(['second'])); await readingFirst
  59. expect(unread.total).toBe(0)
  60. expect(unread.snapshot.conversations).toEqual([])
  61. })
  62. it('ignores a read acknowledgement after the account changes', async () => {
  63. const pending = deferred<UnreadSnapshot>()
  64. vi.mocked(apiRequest).mockResolvedValueOnce(snapshot(['first']))
  65. unread.start('owner'); await flushPromises()
  66. vi.mocked(apiRequest).mockReturnValueOnce(pending.promise as never)
  67. const reading = unread.visible('bot', 'c', undefined, ['first'])
  68. unread.reset('another-owner'); unread.snapshot = snapshot(['new-message'])
  69. pending.resolve(emptyUnread()); await reading
  70. expect(unread.total).toBe(1)
  71. expect(unread.snapshot.conversations[0]?.messages[0]?.id).toBe('new-message')
  72. })