Private
Public Access
Fix desktop tray unread indicator: clear only on genuine room entry
The tray indicator must stay visible while a message is unread and clear only when the user actually opens the room containing it -- never merely because the window was shown, focused, restored, or opened from the tray. Adds window.dsDesktop.roomOpened() (called exactly once per genuine transition to a real room id, via useDesktopRoomBridge) and wires up window.dsDesktop.setUnreadCount() (declared but never actually called before this) to fire whenever the unread-room count changes. Both are no-ops in browser builds, matching this bridge's existing optional- chaining convention. Frontend unread state stays authoritative -- clearRoomIndicators is pulled out of ChatShellPage as a pure function (same behavior, now directly testable) so opening one room only clears that room's unread/mention flags, leaving every other room's state untouched. Also adds a frontend test runner (vitest + React Testing Library) -- none existed in this project before. 10 new tests cover: roomOpened firing once on entry, not refiring on an unrelated rerender with the same room id, refiring on a genuine room-to-room transition, never firing with no room id, setUnreadCount firing independently of roomOpened, clearing one room preserving every other room's unread state, and the desktop bridge remaining fully optional. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
047f47d524
commit
3349cadea2
9 files changed
+1155
-16
No files matched your search
@@ -0,0 +1,68 @@
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import * as desktopBridge from '../lib/desktopBridge'
|
||||
import { useDesktopRoomBridge } from './useDesktopRoomBridge'
|
||||
|
||||
describe('useDesktopRoomBridge', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(desktopBridge, 'notifyRoomOpened')
|
||||
vi.spyOn(desktopBridge, 'notifyUnreadCount')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('calls roomOpened once when entering a room', () => {
|
||||
renderHook(({ roomId, count }) => useDesktopRoomBridge(roomId, count), {
|
||||
initialProps: { roomId: 'room-1', count: 0 },
|
||||
})
|
||||
expect(desktopBridge.notifyRoomOpened).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not call roomOpened again on a rerender with the same roomId', () => {
|
||||
const { rerender } = renderHook(({ roomId, count }) => useDesktopRoomBridge(roomId, count), {
|
||||
initialProps: { roomId: 'room-1', count: 0 },
|
||||
})
|
||||
expect(desktopBridge.notifyRoomOpened).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Unrelated rerender -- unreadRoomCount changed, roomId did not.
|
||||
rerender({ roomId: 'room-1', count: 3 })
|
||||
rerender({ roomId: 'room-1', count: 3 })
|
||||
expect(desktopBridge.notifyRoomOpened).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('calls roomOpened again on a genuine transition to a different room', () => {
|
||||
const { rerender } = renderHook(({ roomId, count }) => useDesktopRoomBridge(roomId, count), {
|
||||
initialProps: { roomId: 'room-1', count: 0 },
|
||||
})
|
||||
rerender({ roomId: 'room-2', count: 0 })
|
||||
expect(desktopBridge.notifyRoomOpened).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('does not call roomOpened when there is no room id', () => {
|
||||
renderHook(({ roomId, count }) => useDesktopRoomBridge(roomId, count), {
|
||||
initialProps: { roomId: undefined as string | undefined, count: 0 },
|
||||
})
|
||||
expect(desktopBridge.notifyRoomOpened).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sends the unread count whenever it changes, independent of roomOpened', () => {
|
||||
const { rerender } = renderHook(({ roomId, count }) => useDesktopRoomBridge(roomId, count), {
|
||||
initialProps: { roomId: undefined as string | undefined, count: 0 },
|
||||
})
|
||||
expect(desktopBridge.notifyUnreadCount).toHaveBeenCalledWith(0)
|
||||
|
||||
rerender({ roomId: undefined, count: 2 })
|
||||
expect(desktopBridge.notifyUnreadCount).toHaveBeenCalledWith(2)
|
||||
expect(desktopBridge.notifyRoomOpened).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('desktop bridge functions with no window.dsDesktop', () => {
|
||||
it('remain optional -- calling them does not throw when the bridge is absent', () => {
|
||||
expect(window.dsDesktop).toBeUndefined()
|
||||
expect(() => desktopBridge.notifyRoomOpened()).not.toThrow()
|
||||
expect(() => desktopBridge.notifyUnreadCount(5)).not.toThrow()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user