import { fireEvent, render, screen, waitFor } from "@testing-library/react"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { SectionExportTab } from "../../components/section-export-tab"
import type { ExportSettings } from "../../types/export-types"
// Мокаем зависимости
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (key: string, params?: any) => {
if (params) {
return `${key} ${JSON.stringify(params)}`
}
return key
},
i18n: { language: "ru" },
}),
}))
// Мокаем lucide-react иконки
vi.mock("lucide-react", async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
Clock: () =>
Clock
,
Flag: () => Flag
,
Play: () => Play
,
Scissors: () => Scissors
,
Video: () => Video
,
}
})
// Мокаем useTimeline хук
const mockSeek = vi.fn()
const mockProject = {
sections: [
{
id: "section-1",
name: "Section 1",
startTime: 0,
endTime: 60,
tracks: [
{
id: "track-1",
name: "Track 1",
clips: [
{
id: "clip-1",
name: "Clip 1",
startTime: 10,
duration: 20,
},
{
id: "clip-2",
name: "Clip 2",
startTime: 40,
duration: 15,
},
],
},
],
},
{
id: "section-2",
name: "Section 2",
startTime: 60,
endTime: 120,
tracks: [
{
id: "track-2",
name: "Track 2",
clips: [
{
id: "clip-3",
name: "Clip 3",
startTime: 5,
duration: 30,
},
],
},
],
},
],
markers: [
{ id: "marker-1", name: "Marker 1", time: 15 },
{ id: "marker-2", name: "Marker 2", time: 45 },
{ id: "marker-3", name: "Marker 3", time: 90 },
],
duration: 180,
}
vi.mock("@/features/timeline/hooks/use-timeline", () => ({
useTimeline: () => ({
project: mockProject,
seek: mockSeek,
}),
}))
describe("SectionExportTab", () => {
const defaultSettings: ExportSettings = {
format: "mp4",
codec: "h264",
resolution: "1920x1080",
fps: 30,
bitrate: 8000,
bitrateMode: "vbr",
quality: "high",
preset: "medium",
outputPath: "/test/path",
fileName: "export",
includeAudio: true,
includeSubtitles: true,
audioCodec: "aac",
audioBitrate: 192,
channels: "stereo",
sampleRate: 48000,
pixelFormat: "yuv420p",
colorSpace: "bt709",
startFrame: 0,
endFrame: null,
includeAlpha: false,
twoPass: false,
hardwareAcceleration: false,
preserveMetadata: true,
}
const mockOnExport = vi.fn()
const mockOnPreviewSection = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
describe("Рендеринг", () => {
it("должен рендерить основные элементы", () => {
render()
expect(screen.getByText("export.sections.exportMode")).toBeInTheDocument()
expect(screen.getByText("export.sections.qualityPreset")).toBeInTheDocument()
expect(screen.getByText("export.sections.byMarkers")).toBeInTheDocument()
expect(screen.getByText("export.sections.byClips")).toBeInTheDocument()
expect(screen.getByText("export.sections.manual")).toBeInTheDocument()
})
it("должен показывать поля ввода времени в ручном режиме", () => {
render()
const manualRadio = screen.getByRole("radio", { name: /export.sections.manual/ })
fireEvent.click(manualRadio)
expect(screen.getByText("export.sections.startTime")).toBeInTheDocument()
expect(screen.getByText("export.sections.endTime")).toBeInTheDocument()
expect(screen.getByText("export.sections.createSection")).toBeInTheDocument()
})
})
describe("Режим маркеров", () => {
it("должен создавать секции из маркеров", async () => {
render()
// По умолчанию выбран режим маркеров
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
// Проверяем что созданы секции из маркеров
expect(screen.getByDisplayValue("Marker 1")).toBeInTheDocument()
expect(screen.getByDisplayValue("Marker 2")).toBeInTheDocument()
expect(screen.getByDisplayValue("Marker 3")).toBeInTheDocument()
})
it("должен использовать секции проекта если нет маркеров", async () => {
// Временно удаляем маркеры
const originalMarkers = mockProject.markers
mockProject.markers = []
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
// Проверяем что используются секции проекта
expect(screen.getByDisplayValue("Section 1")).toBeInTheDocument()
expect(screen.getByDisplayValue("Section 2")).toBeInTheDocument()
// Восстанавливаем маркеры
mockProject.markers = originalMarkers
})
})
describe("Режим клипов", () => {
it("должен создавать секции из клипов", async () => {
render()
const clipsRadio = screen.getByRole("radio", { name: /export.sections.byClips/ })
fireEvent.click(clipsRadio)
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
// Проверяем что созданы секции из клипов
expect(screen.getByDisplayValue("Clip 1")).toBeInTheDocument()
expect(screen.getByDisplayValue("Clip 2")).toBeInTheDocument()
expect(screen.getByDisplayValue("Clip 3")).toBeInTheDocument()
})
})
describe("Ручной режим", () => {
it("должен создавать секцию с указанным временем", async () => {
render()
const manualRadio = screen.getByRole("radio", { name: /export.sections.manual/ })
fireEvent.click(manualRadio)
const startInput = screen.getByPlaceholderText("00:00:00")
const endInput = screen.getByPlaceholderText("00:00:10")
fireEvent.change(startInput, { target: { value: "00:01:00" } })
fireEvent.change(endInput, { target: { value: "00:02:30" } })
const createButton = screen.getByText("export.sections.createSection")
fireEvent.click(createButton)
await waitFor(() => {
expect(screen.getByDisplayValue("Manual Section")).toBeInTheDocument()
})
})
it("не должен создавать секцию если время начала больше времени конца", () => {
render()
const manualRadio = screen.getByRole("radio", { name: /export.sections.manual/ })
fireEvent.click(manualRadio)
const startInput = screen.getByPlaceholderText("00:00:00")
const endInput = screen.getByPlaceholderText("00:00:10")
fireEvent.change(startInput, { target: { value: "00:02:00" } })
fireEvent.change(endInput, { target: { value: "00:01:00" } })
const createButton = screen.getByText("export.sections.createSection")
fireEvent.click(createButton)
// Секция не должна быть создана
expect(screen.queryByDisplayValue("Manual Section")).not.toBeInTheDocument()
})
})
describe("Управление секциями", () => {
it("должен переключать выбор секции", async () => {
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
const checkboxes = screen.getAllByRole("checkbox")
expect(checkboxes[0]).toBeChecked()
fireEvent.click(checkboxes[0])
expect(checkboxes[0]).not.toBeChecked()
fireEvent.click(checkboxes[0])
expect(checkboxes[0]).toBeChecked()
})
it("должен выбирать/снимать выбор со всех секций", async () => {
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
const selectAllButton = screen.getByText("export.sections.deselectAll")
fireEvent.click(selectAllButton)
const checkboxes = screen.getAllByRole("checkbox")
checkboxes.forEach((checkbox) => {
expect(checkbox).not.toBeChecked()
})
fireEvent.click(screen.getByText("export.sections.selectAll"))
checkboxes.forEach((checkbox) => {
expect(checkbox).toBeChecked()
})
})
it("должен обновлять имя файла секции", async () => {
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
const nameInput = screen.getByDisplayValue("Marker 1")
fireEvent.change(nameInput, { target: { value: "Custom Name" } })
expect(screen.getByDisplayValue("Custom Name")).toBeInTheDocument()
})
it("должен вызывать предпросмотр секции", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
const previewButtons = screen.getAllByTitle("export.sections.preview")
fireEvent.click(previewButtons[0])
expect(mockOnPreviewSection).toHaveBeenCalledWith(15)
})
it("должен использовать seek если не передан onPreviewSection", async () => {
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
const previewButtons = screen.getAllByTitle("export.sections.preview")
fireEvent.click(previewButtons[0])
expect(mockSeek).toHaveBeenCalledWith(15)
})
})
describe("Настройки качества", () => {
it("должен менять настройки качества", () => {
render()
const qualitySelect = screen.getByRole("combobox")
fireEvent.click(qualitySelect)
const previewOption = screen.getByText("export.sections.preview")
fireEvent.click(previewOption)
// Проверяем что выбран preview
expect(screen.getByText("export.sections.preview")).toBeInTheDocument()
})
})
describe("Экспорт", () => {
it("должен вызывать onExport с выбранными секциями", async () => {
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
const exportButton = screen.getByText(/export.sections.exportSections/)
fireEvent.click(exportButton)
expect(mockOnExport).toHaveBeenCalledWith(
expect.objectContaining({
sections: expect.arrayContaining([
expect.objectContaining({
name: "Marker 1",
includeInExport: true,
}),
]),
}),
)
})
it("должен отключать кнопку экспорта если не выбрано секций", async () => {
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
// Снимаем выбор со всех
const selectAllButton = screen.getByText("export.sections.deselectAll")
fireEvent.click(selectAllButton)
const exportButton = screen.getByText(/export.sections.exportSections/)
expect(exportButton).toBeDisabled()
})
it("должен применять настройки качества preview", async () => {
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
// Меняем качество на preview
const qualitySelect = screen.getByRole("combobox")
fireEvent.click(qualitySelect)
const previewOption = screen.getByText("export.sections.preview")
fireEvent.click(previewOption)
const exportButton = screen.getByText(/export.sections.exportSections/)
fireEvent.click(exportButton)
expect(mockOnExport).toHaveBeenCalledWith(
expect.objectContaining({
resolution: "720",
bitrate: 2000,
bitrateMode: "vbr",
quality: "normal",
}),
)
})
it("должен применять настройки качества draft", async () => {
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
// Меняем качество на draft
const qualitySelect = screen.getByRole("combobox")
fireEvent.click(qualitySelect)
const draftOption = screen.getByText("export.sections.draft")
fireEvent.click(draftOption)
const exportButton = screen.getByText(/export.sections.exportSections/)
fireEvent.click(exportButton)
expect(mockOnExport).toHaveBeenCalledWith(
expect.objectContaining({
resolution: "1080",
bitrate: 5000,
bitrateMode: "vbr",
quality: "good",
}),
)
})
})
describe("Форматирование времени", () => {
it("должен правильно отображать время секций", async () => {
render()
await waitFor(() => {
expect(screen.getByText("export.sections.sectionsTitle")).toBeInTheDocument()
})
// Проверяем форматирование времени
expect(screen.getByText(/0:15 - 0:45/)).toBeInTheDocument() // Marker 1
expect(screen.getByText(/0:45 - 1:30/)).toBeInTheDocument() // Marker 2
})
})
})