121 lines
4.5 KiB
TypeScript
121 lines
4.5 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act, StrictMode } from 'react'
|
|
import { createRoot, type Root } from 'react-dom/client'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { findCallerByNumber, type StoredAddress } from '../addresses'
|
|
import type { CallEvent } from '../swyx/protocol'
|
|
import { useCallerLookup } from './useCallerLookup'
|
|
|
|
vi.mock('../addresses', () => ({ findCallerByNumber: vi.fn() }))
|
|
|
|
const ringing: CallEvent = { line: 1, callId: 42, event: 'incoming', peerNumber: '+493012345' }
|
|
const customer: StoredAddress = { number: ringing.peerNumber, name: 'Kunde', role: 'customer', jobIds: [4711] }
|
|
|
|
function deferred() {
|
|
let resolve!: (value: StoredAddress | null) => void
|
|
let reject!: (reason: Error) => void
|
|
const promise = new Promise<StoredAddress | null>((yes, no) => { resolve = yes; reject = no })
|
|
return { promise, resolve, reject }
|
|
}
|
|
|
|
describe('useCallerLookup', () => {
|
|
let root: Root
|
|
let element: HTMLDivElement
|
|
let current: ReturnType<typeof useCallerLookup>
|
|
|
|
function Probe({ call }: { call: CallEvent | null }) {
|
|
current = useCallerLookup(call)
|
|
return <div>{current.caller?.name ?? current.caller?.number ?? ''}</div>
|
|
}
|
|
|
|
async function render(call: CallEvent | null, strict = false) {
|
|
await act(async () => {
|
|
root.render(strict ? <StrictMode><Probe call={call} /></StrictMode> : <Probe call={call} />)
|
|
})
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
|
|
vi.mocked(findCallerByNumber).mockReset()
|
|
element = document.createElement('div')
|
|
document.body.append(element)
|
|
root = createRoot(element)
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await act(async () => root.unmount())
|
|
element.remove()
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
it('shows the lookup result even when another snapshot arrives before it', async () => {
|
|
const request = deferred()
|
|
vi.mocked(findCallerByNumber).mockReturnValue(request.promise)
|
|
await render(ringing)
|
|
await render({ ...ringing, stateText: 'klingelt' })
|
|
await render({ ...ringing })
|
|
expect(findCallerByNumber).toHaveBeenCalledTimes(1)
|
|
|
|
await act(async () => request.resolve(customer))
|
|
expect(element.textContent).toBe('Kunde')
|
|
expect(current.caller?.jobIds).toEqual([4711])
|
|
})
|
|
|
|
it('ignores the previous lookup when the line is reused, even for the same number', async () => {
|
|
const previous = deferred(), next = deferred()
|
|
vi.mocked(findCallerByNumber).mockReturnValueOnce(previous.promise).mockReturnValueOnce(next.promise)
|
|
await render(ringing)
|
|
await render({ ...ringing, callId: 43 })
|
|
await act(async () => previous.resolve(customer))
|
|
expect(current.caller).toBeNull()
|
|
|
|
await act(async () => next.resolve({ ...customer, name: 'Aktueller Kunde' }))
|
|
expect(element.textContent).toBe('Aktueller Kunde')
|
|
expect(findCallerByNumber).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('does not open a late popup after ringing ends', async () => {
|
|
const request = deferred()
|
|
vi.mocked(findCallerByNumber).mockReturnValue(request.promise)
|
|
await render(ringing)
|
|
await render(null)
|
|
await act(async () => request.resolve(customer))
|
|
expect(current.caller).toBeNull()
|
|
})
|
|
|
|
it('keeps loaded customer details after ringing ends', async () => {
|
|
vi.mocked(findCallerByNumber).mockResolvedValue(customer)
|
|
await render(ringing)
|
|
await render(null)
|
|
expect(current.caller).toEqual(customer)
|
|
})
|
|
|
|
it('keeps a dismissed popup closed until the next call', async () => {
|
|
const legacyCall = { ...ringing, callId: undefined }
|
|
vi.mocked(findCallerByNumber).mockResolvedValue(customer)
|
|
await render(legacyCall)
|
|
await act(async () => current.dismissCaller())
|
|
await render({ ...legacyCall })
|
|
expect(current.caller).toBeNull()
|
|
expect(findCallerByNumber).toHaveBeenCalledTimes(1)
|
|
|
|
// Auch ältere Tray-Versionen ohne callId dürfen denselben Anschluss erneut melden.
|
|
await render(null)
|
|
await render(legacyCall)
|
|
expect(current.caller).toEqual(customer)
|
|
expect(findCallerByNumber).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('falls back to the phone number on a lookup failure', async () => {
|
|
vi.mocked(findCallerByNumber).mockRejectedValue(new Error('Backend unavailable'))
|
|
await render(ringing)
|
|
expect(current.caller).toEqual({ number: ringing.peerNumber })
|
|
})
|
|
|
|
it('still loads the caller when React StrictMode restarts the effect', async () => {
|
|
vi.mocked(findCallerByNumber).mockResolvedValue(customer)
|
|
await render(ringing, true)
|
|
expect(element.textContent).toBe('Kunde')
|
|
})
|
|
})
|