Add i18n, tests, and update documentation

Introduces internationalization (i18n) support with English and Turkish, adds unit tests and test setup with Vitest and React Testing Library, and updates documentation including README and changelog. Removes legacy publishing and usage guides, refactors components to use translation system, and updates build and test scripts in package.json. Also adds new utility modules for HLS and CORS, and improves PlayerContext and SettingsMenu for language support.
This commit is contained in:
hibna
2025-10-29 13:10:07 +03:00
parent e75d241421
commit bad1cc6ca0
26 changed files with 1843 additions and 1341 deletions
+131
View File
@@ -0,0 +1,131 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { VideoPlayer } from './VideoPlayer';
describe('VideoPlayer', () => {
const defaultProps = {
src: 'https://example.com/video.mp4',
};
it('renders video player container', () => {
const { container } = render(<VideoPlayer {...defaultProps} />);
expect(container.querySelector('.video-player')).toBeInTheDocument();
});
it('renders video element', () => {
const { container } = render(<VideoPlayer {...defaultProps} />);
const video = container.querySelector('video');
expect(video).toBeInTheDocument();
});
it('renders with autoplay prop', () => {
const { container } = render(<VideoPlayer {...defaultProps} autoplay />);
const video = container.querySelector('video');
// VideoElement handles autoplay programmatically via play() method
expect(video).toBeInTheDocument();
});
it('renders with muted prop', () => {
const { container } = render(<VideoPlayer {...defaultProps} muted />);
const video = container.querySelector('video');
// Muted state is managed through VideoElement
expect(video).toBeInTheDocument();
});
it('applies loop when enabled', () => {
const { container } = render(<VideoPlayer {...defaultProps} loop />);
const video = container.querySelector('video');
expect(video).toHaveAttribute('loop');
});
it('applies custom className', () => {
const className = 'custom-player';
const { container } = render(<VideoPlayer {...defaultProps} className={className} />);
expect(container.querySelector('.video-player')).toHaveClass('video-player', className);
});
it('calls onPlay callback when play event fires', async () => {
const onPlay = vi.fn();
const { container } = render(<VideoPlayer {...defaultProps} onPlay={onPlay} />);
const video = container.querySelector('video') as HTMLVideoElement;
video.dispatchEvent(new Event('play'));
await waitFor(() => {
expect(onPlay).toHaveBeenCalled();
});
});
it('calls onPause callback when pause event fires', async () => {
const onPause = vi.fn();
const { container } = render(<VideoPlayer {...defaultProps} onPause={onPause} />);
const video = container.querySelector('video') as HTMLVideoElement;
video.dispatchEvent(new Event('pause'));
await waitFor(() => {
expect(onPause).toHaveBeenCalled();
});
});
it('calls onEnded callback when ended event fires', async () => {
const onEnded = vi.fn();
const { container } = render(<VideoPlayer {...defaultProps} onEnded={onEnded} />);
const video = container.querySelector('video') as HTMLVideoElement;
video.dispatchEvent(new Event('ended'));
await waitFor(() => {
expect(onEnded).toHaveBeenCalled();
});
});
it('calls onTimeUpdate callback with current time', async () => {
const onTimeUpdate = vi.fn();
const { container } = render(<VideoPlayer {...defaultProps} onTimeUpdate={onTimeUpdate} />);
const video = container.querySelector('video') as HTMLVideoElement;
Object.defineProperty(video, 'currentTime', { value: 10.5, configurable: true });
video.dispatchEvent(new Event('timeupdate'));
await waitFor(() => {
expect(onTimeUpdate).toHaveBeenCalledWith(10.5);
});
});
it('renders with subtitles prop', () => {
const subtitles = [
{ src: 'subtitles-en.vtt', srcLang: 'en', label: 'English' },
{ src: 'subtitles-tr.vtt', srcLang: 'tr', label: 'Türkçe' },
];
const { container } = render(<VideoPlayer {...defaultProps} subtitles={subtitles} />);
const video = container.querySelector('video') as HTMLVideoElement;
// Subtitles are added dynamically by VideoElement
expect(video).toBeInTheDocument();
});
it('renders without errors', () => {
const onError = vi.fn();
const { container } = render(<VideoPlayer {...defaultProps} onError={onError} />);
const video = container.querySelector('video') as HTMLVideoElement;
expect(video).toBeInTheDocument();
// Error handling is tested separately in integration tests
});
it('hides controls when controls prop is false', () => {
const { container } = render(<VideoPlayer {...defaultProps} controls={false} />);
const controls = container.querySelector('.controls');
expect(controls).not.toBeInTheDocument();
});
it('applies custom style', () => {
const style = { width: '800px', height: '450px' };
const { container } = render(<VideoPlayer {...defaultProps} style={style} />);
const playerElement = container.querySelector('.video-player') as HTMLElement;
expect(playerElement.style.width).toBe('800px');
expect(playerElement.style.height).toBe('450px');
});
});