Private
Public Access
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>
69 lines
2.7 KiB
TypeScript
69 lines
2.7 KiB
TypeScript
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()
|
|
})
|
|
})
|