// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { act } from '@testing-library/react'; import { renderHook } from '@testing-library/react'; import { AudioVideoObserver, DefaultAudioVideoFacade, ClientMetricReport, NoOpAudioVideoController, NoOpDebugLogger, } from 'amazon-chime-sdk-js'; import React from 'react'; import { AudioVideoContext } from '../../../src'; import useMediaStreamMetrics from '../../../src/hooks/sdk/useMediaStreamMetrics'; class FakeClientMetricReport extends ClientMetricReport { constructor() { super(new NoOpDebugLogger()); } getObservableVideoMetrics(): { [p: string]: { [p: string]: {} } } { return { 1: { 1: { videoDownstreamBitrate: 8000, videoDownstreamPacketLossPercent: 50, videoDownstreamPacketsReceived: 1000, }, }, }; } getObservableMetrics(): { [p: string]: number } { return { audioPacketsSentFractionLossPercent: NaN, audioPacketsReceivedFractionLossPercent: NaN, audioSpeakerDelayMs: NaN, audioUpstreamRoundTripTimeMs: NaN, audioUpstreamJitterMs: NaN, audioDownstreamJitterMs: NaN, currentRoundTripTimeMs: 11.11, availableOutgoingBitrate: 1111, availableIncomingBitrate: 1111, }; } getRTCStatsReport(): RTCStatsReport { return {} as RTCStatsReport; } } class NoOPAudioVideoFacade extends DefaultAudioVideoFacade { observerQueue: Set = new Set(); constructor() { super( new NoOpAudioVideoController(), // @ts-ignore undefined, undefined, undefined, undefined, undefined ); } addObserver(observer: AudioVideoObserver): void { this.observerQueue.add(observer); } removeObserver(observer: AudioVideoObserver): void { this.observerQueue.delete(observer); } publicMetrics(): void { const clientMetricReport = new FakeClientMetricReport(); for (const observer of this.observerQueue) { if (observer.metricsDidReceive) { observer.metricsDidReceive(clientMetricReport); } } } } const initialMediaStreamMetrics = { audioPacketsSentFractionLossPercent: null, audioPacketsReceivedFractionLossPercent: null, audioSpeakerDelayMs: null, audioUpstreamRoundTripTimeMs: null, audioUpstreamJitterMs: null, audioDownstreamJitterMs: null, currentRoundTripTimeMs: null, availableOutgoingBandwidth: null, availableIncomingBandwidth: null, rtcStatsReport: null, videoStreamMetrics: {}, }; const expectedMediaStreamMetrics = { audioPacketsSentFractionLossPercent: 0, audioPacketsReceivedFractionLossPercent: 0, audioSpeakerDelayMs: 0, audioUpstreamRoundTripTimeMs: 0, audioUpstreamJitterMs: 0, audioDownstreamJitterMs: 0, currentRoundTripTimeMs: 11, availableOutgoingBandwidth: 1, availableIncomingBandwidth: 1, rtcStatsReport: {}, videoStreamMetrics: { '1': { '1': { videoDownstreamBitrate: 8000, videoDownstreamPacketLossPercent: 50, videoDownstreamPacketsReceived: 1000, }, }, }, }; describe('useMediaStreamMetrics', () => { it('mount/unmount', () => { const audioVideoFacade = new NoOPAudioVideoFacade(); const addObserverSpy = jest.spyOn(audioVideoFacade, 'addObserver'); const removeObserverSpy = jest.spyOn(audioVideoFacade, 'addObserver'); const { result, unmount } = renderHook(() => useMediaStreamMetrics(), { wrapper: ({ children }) => ( {children} ), }); expect(addObserverSpy).toHaveBeenCalledTimes(1); expect(result.current).toStrictEqual(initialMediaStreamMetrics); unmount(); expect(removeObserverSpy).toHaveBeenCalledTimes(1); }); it('returns correct metrics value', () => { const audioVideoFacade = new NoOPAudioVideoFacade(); const { result } = renderHook(() => useMediaStreamMetrics(), { wrapper: ({ children }) => ( {children} ), }); expect(result.current).toStrictEqual(initialMediaStreamMetrics); act(() => { audioVideoFacade.publicMetrics(); }); expect(result.current).toStrictEqual(expectedMediaStreamMetrics); }); });