import React from 'react';
import { render, screen, cleanup } from '@testing-library/react';
import ReactHlsPlayer from '../src/components/ReactHlsPlayer';
import '@testing-library/jest-dom';
import Hls from 'hls.js';
describe('ReactHlsPlayer', () => {
let consoleWarnSpy: jest.SpyInstance;
let capturedWarnings: string[];
const mockSrc =
'https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8';
beforeEach(() => {
jest.clearAllMocks();
capturedWarnings = [];
consoleWarnSpy = jest
.spyOn(console, 'warn')
.mockImplementation((message) => {
capturedWarnings.push(message);
});
});
afterEach(() => {
consoleWarnSpy.mockRestore();
cleanup();
});
it('renders video element', () => {
render();
const videoElement = screen.getByTestId('react-hls-player');
expect(videoElement).toBeInTheDocument();
expect(capturedWarnings).toHaveLength(0);
});
it('initializes Hls when supported', () => {
let hlsInstance = null as Hls | null;
const getHLSInstance = (hls: Hls) => {
hlsInstance = hls;
};
render();
expect(hlsInstance).not.toBeNull();
expect(hlsInstance?.attachMedia).toHaveBeenCalled();
expect(hlsInstance?.loadSource).toHaveBeenCalled();
expect(capturedWarnings).toHaveLength(0);
});
it('renders fallback content when HLS is not supported', async () => {
(Hls.isSupported as jest.Mock).mockReturnValueOnce(false);
render();
const videoElement = screen.getByTestId('react-hls-player-fallback');
expect(videoElement).toHaveAttribute('src', mockSrc);
expect(videoElement).toHaveTextContent(
'Your browser does not support the video tag.'
);
expect(capturedWarnings).toHaveLength(0);
});
it('handles autoplay correctly when autoplay is passed as true', () => {
const mockPlay = jest.fn().mockImplementation(async () => {});
let hlsInstance: Hls | null = null;
const getHLSInstance = (hls: Hls) => {
hlsInstance = hls;
};
jest.spyOn(HTMLVideoElement.prototype, 'play').mockImplementation(mockPlay);
render(
);
expect(hlsInstance).not.toBeNull();
expect(mockPlay).toHaveBeenCalled();
expect(capturedWarnings).toHaveLength(0);
mockPlay.mockClear();
});
it('handles autoplay correctly when autoplay is passed as false', () => {
const mockPlay = jest.fn().mockImplementation(async () => {});
let hlsInstance: Hls | null = null;
const getHLSInstance = (hls: Hls) => {
hlsInstance = hls;
};
jest.spyOn(HTMLVideoElement.prototype, 'play').mockImplementation(mockPlay);
render();
expect(hlsInstance).not.toBeNull();
expect(mockPlay).not.toHaveBeenCalled();
expect(capturedWarnings).toHaveLength(0);
mockPlay.mockClear();
});
it('calls recoverMediaError function on fatal MEDIA_ERROR', () => {
let hlsInstance = null as Hls | null;
const getHLSInstance = (hls: Hls) => {
hlsInstance = hls;
};
render();
expect(hlsInstance).not.toBeNull();
hlsInstance!.emit(Hls.Events.ERROR, Hls.Events.ERROR, {
fatal: true,
type: Hls.ErrorTypes.MEDIA_ERROR,
details: Hls.ErrorDetails.LEVEL_EMPTY_ERROR,
error: { name: 'Media Error', message: 'This is a test media error' },
});
expect(hlsInstance?.recoverMediaError).toHaveBeenCalled();
expect(capturedWarnings).toHaveLength(1);
});
it('calls start load function on fatal MEDIA_ERROR', () => {
let hlsInstance = null as Hls | null;
const getHLSInstance = (hls: Hls) => {
hlsInstance = hls;
};
render();
expect(hlsInstance).not.toBeNull();
hlsInstance!.emit(Hls.Events.ERROR, Hls.Events.ERROR, {
fatal: true,
type: Hls.ErrorTypes.NETWORK_ERROR,
details: Hls.ErrorDetails.BUFFER_NUDGE_ON_STALL,
error: { name: 'Network Error', message: 'This is a test network error' },
});
expect(hlsInstance?.startLoad).toHaveBeenCalled();
expect(capturedWarnings).toHaveLength(1);
});
it('calls no functions on non-fatal ERROR', () => {
let hlsInstance = null as Hls | null;
const getHLSInstance = (hls: Hls) => {
hlsInstance = hls;
};
render();
expect(hlsInstance).not.toBeNull();
hlsInstance!.emit(Hls.Events.ERROR, Hls.Events.ERROR, {
fatal: false,
type: Hls.ErrorTypes.OTHER_ERROR,
details: Hls.ErrorDetails.UNKNOWN,
error: { name: 'Unknown Error', message: 'This is a test unknown error' },
});
expect(hlsInstance?.startLoad).not.toHaveBeenCalled();
expect(hlsInstance?.recoverMediaError).not.toHaveBeenCalled();
expect(capturedWarnings).toHaveLength(0);
});
});