Anrufer- und Webhook-Verarbeitung verbessern
This commit is contained in:
1 parent
7b3c187985
commit
9e71fe3a11
18 files changed
+1283
-111
No files matched your search
@@ -0,0 +1,120 @@
|
||||
// @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')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user