import { render, screen } from "@testing-library/react"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { MediaFile } from "@/features/media/types/media"
import { VideoPlayer } from "../../components/video-player"
// Мокаем Tauri API
vi.mock("@tauri-apps/api/core", () => ({
convertFileSrc: vi.fn((path: string) => path),
}))
// Мокаем tauri-utils
vi.mock("@/lib/tauri-utils", () => ({
convertVideoSrc: vi.fn((path: string) => path),
isTauriEnvironment: vi.fn(() => false),
}))
// Мокаем AspectRatio компонент
vi.mock("@/components/ui/aspect-ratio", () => ({
AspectRatio: ({ children, ratio, className }: any) => (
{children}
),
}))
// Мокаем PlayerControls
vi.mock("../../components/player-controls", () => ({
PlayerControls: ({ currentTime, file }: any) => (
Player Controls
),
}))
// Мокаем хуки
const mockProjectSettings = {
settings: {
aspectRatio: {
value: { width: 16, height: 9 },
},
},
}
vi.mock("@/features/project-settings", () => ({
useProjectSettings: () => mockProjectSettings,
}))
const mockPlayerContext = {
currentVideo: null as MediaFile | null,
isPlaying: false,
currentTime: 0,
duration: 0,
volume: 100,
playbackRate: 1,
appliedEffects: [],
appliedFilters: [],
appliedTemplate: null,
videoSource: "browser" as "browser" | "timeline",
previewMedia: null,
isVideoLoading: false,
isVideoReady: true,
isSeeking: false,
play: vi.fn().mockResolvedValue(undefined),
pause: vi.fn().mockResolvedValue(undefined),
seek: vi.fn().mockResolvedValue(undefined),
setVolume: vi.fn(),
setPlaybackRate: vi.fn().mockResolvedValue(undefined),
applyEffect: vi.fn(),
applyFilter: vi.fn(),
applyTemplate: vi.fn(),
clearEffects: vi.fn(),
clearFilters: vi.fn(),
clearTemplate: vi.fn(),
setPreviewMedia: vi.fn(),
setVideoSource: vi.fn(),
}
vi.mock("../../services/player-provider", () => ({
usePlayer: () => mockPlayerContext,
}))
// Мокаем хук AI интеграции
vi.mock("@/features/ai-chat/hooks/use-player-ai-integration", () => ({
usePlayerAIIntegration: () => ({
isReady: true,
hasMedia: false,
isPlaying: false,
effectsCount: 0,
filtersCount: 0,
}),
}))
describe("VideoPlayer", () => {
beforeEach(() => {
vi.clearAllMocks()
mockPlayerContext.currentVideo = null
mockPlayerContext.previewMedia = null
mockPlayerContext.appliedEffects = []
mockPlayerContext.appliedFilters = []
mockPlayerContext.appliedTemplate = null
mockPlayerContext.isVideoReady = true
})
describe("Без видео", () => {
it("должен отображать плеер без видео", () => {
render()
const video = document.querySelector("video")!
expect(video).toBeInTheDocument()
expect(video).toHaveAttribute("src", "#")
expect(video).not.toHaveAttribute("controls") // controls={false} не добавляет атрибут
expect(video).not.toHaveAttribute("muted") // muted={false} не добавляет атрибут
})
it("должен отображать контролы с нулевым временем", () => {
render()
const controls = screen.getByTestId("player-controls")
expect(controls).toBeInTheDocument()
expect(controls).toHaveAttribute("data-current-time", "0")
expect(controls).toHaveAttribute("data-file-name", "Нет видео")
})
it("должен использовать правильное соотношение сторон", () => {
render()
const aspectRatio = screen.getByTestId("aspect-ratio")
expect(aspectRatio).toBeInTheDocument()
expect(aspectRatio).toHaveAttribute("data-ratio", "1.7777777777777777") // 16/9
})
it("должен применять правильные стили к контейнеру", () => {
render()
const container = document.querySelector("video")!.closest(".media-player-container")
expect(container?.className).toMatch(/relative/)
expect(container?.className).toMatch(/flex/)
expect(container?.className).toMatch(/h-full/)
expect(container?.className).toMatch(/flex-col/)
})
})
describe("С видео", () => {
const mockVideo: MediaFile = {
id: "test-video",
path: "/path/to/video.mp4",
name: "Test Video.mp4",
size: 1024000,
isVideo: true,
duration: 60000,
}
beforeEach(() => {
mockPlayerContext.currentVideo = mockVideo
})
it("должен отображать видео с правильным src", () => {
render()
const video = document.querySelector("video")!
expect(video).toBeInTheDocument()
expect(video).toHaveAttribute("src", "/path/to/video.mp4")
})
it("должен передавать правильные атрибуты видео", () => {
render()
const video = document.querySelector("video")!
expect(video).not.toHaveAttribute("controls") // controls={false}
expect(video).not.toHaveAttribute("autoplay") // autoPlay={false}
expect(video).not.toHaveAttribute("loop") // loop={false}
expect(video).toHaveAttribute("preload", "auto")
expect(video).toHaveAttribute("playsinline") // playsInline={true}
expect(video).not.toHaveAttribute("muted") // muted={false}
})
it("должен передавать видео файл в контролы", () => {
render()
const controls = screen.getByTestId("player-controls")
expect(controls).toHaveAttribute("data-file-name", "Test Video.mp4")
})
it("должен применять правильные классы к видео", () => {
render()
const video = document.querySelector("video")!
expect(video.className).toMatch(/absolute/)
expect(video.className).toMatch(/inset-0/)
expect(video.className).toMatch(/h-full/)
expect(video.className).toMatch(/w-full/)
})
})
describe("Различные соотношения сторон", () => {
it("должен правильно вычислять соотношение 4:3", () => {
mockProjectSettings.settings.aspectRatio.value = { width: 4, height: 3 }
render()
const aspectRatio = screen.getByTestId("aspect-ratio")
expect(aspectRatio).toHaveAttribute("data-ratio", "1.3333333333333333")
})
it("должен правильно вычислять соотношение 21:9", () => {
mockProjectSettings.settings.aspectRatio.value = { width: 21, height: 9 }
render()
const aspectRatio = screen.getByTestId("aspect-ratio")
expect(aspectRatio).toHaveAttribute("data-ratio", "2.3333333333333335")
})
it("должен правильно вычислять соотношение 1:1", () => {
mockProjectSettings.settings.aspectRatio.value = { width: 1, height: 1 }
render()
const aspectRatio = screen.getByTestId("aspect-ratio")
expect(aspectRatio).toHaveAttribute("data-ratio", "1")
})
})
describe("Стили и разметка", () => {
it("должен иметь правильную структуру DOM", () => {
render()
const container = document.querySelector(".media-player-container")
expect(container).toBeInTheDocument()
const videoContainer = container?.querySelector(".bg-black")
expect(videoContainer).toBeInTheDocument()
expect(videoContainer?.className).toMatch(/relative/)
expect(videoContainer?.className).toMatch(/flex-1/)
const aspectRatio = screen.getByTestId("aspect-ratio")
expect(aspectRatio.className).toMatch(/bg-black/)
})
it("должен применять inline стили к видео", () => {
render()
const video = document.querySelector("video")!
const style = video.getAttribute("style")
expect(style).toContain("position: absolute")
expect(style).toContain("top: 0")
expect(style).toContain("left: 0")
expect(style).toContain("width: 100%")
expect(style).toContain("height: 100%")
expect(style).toContain("display: block")
})
it("должен иметь правильную вложенность элементов", () => {
render()
const container = document.querySelector(".media-player-container")
const videoWrapper = container?.querySelector(".bg-black")
const flexContainer = videoWrapper?.querySelector(".flex")
const aspectRatio = flexContainer?.querySelector('[data-testid="aspect-ratio"]')
const video = aspectRatio?.querySelector("video")
expect(container).toBeInTheDocument()
expect(videoWrapper).toBeInTheDocument()
expect(flexContainer).toBeInTheDocument()
expect(aspectRatio).toBeInTheDocument()
expect(video).toBeInTheDocument()
})
})
describe("Переключение видео", () => {
it("должен обновлять видео при изменении", () => {
const { rerender } = render()
const videoInitial = document.querySelector("video")!
expect(videoInitial).toHaveAttribute("src", "#")
// Устанавливаем видео
mockPlayerContext.currentVideo = {
id: "new-video",
path: "/new/video.mp4",
name: "New Video.mp4",
size: 2048000,
isVideo: true,
duration: 120000,
}
rerender()
const videoUpdated = document.querySelector("video")!
expect(videoUpdated).toHaveAttribute("src", "/new/video.mp4")
// key атрибут не появляется в DOM
})
it("должен возвращаться к пустому состоянию при удалении видео", () => {
mockPlayerContext.currentVideo = {
id: "temp-video",
path: "/temp/video.mp4",
name: "Temp Video.mp4",
size: 1024000,
isVideo: true,
duration: 30000,
}
const { rerender } = render()
const videoWithFile = document.querySelector("video")!
expect(videoWithFile).toHaveAttribute("src", "/temp/video.mp4")
// Удаляем видео
mockPlayerContext.currentVideo = null
rerender()
const videoEmpty = document.querySelector("video")!
expect(videoEmpty).toHaveAttribute("src", "#")
})
})
describe("Обработка edge cases", () => {
it("должен обрабатывать видео без id", () => {
mockPlayerContext.video = {
id: "",
path: "/path/to/video.mp4",
name: "Video without ID.mp4",
size: 1024000,
isVideo: true,
duration: 45000,
}
render()
const video = document.querySelector("video")!
expect(video).toBeInTheDocument() // key не виден в DOM
})
it("должен обрабатывать нулевое соотношение сторон", () => {
mockProjectSettings.settings.aspectRatio.value = { width: 0, height: 0 }
// Не должно вызвать ошибку деления на ноль
expect(() => render()).not.toThrow()
})
})
})