import { render, screen } from "@testing-library/react"
import { describe, expect, it, vi } from "vitest"
// Import mocks before components
import "../../../__mocks__/hooks"
import { Clip } from "../../../components/clip/clip"
import { SubtitleClip, TimelineClip, TimelineTrack } from "../../../types"
// Мокаем дочерние компоненты
vi.mock("../../../components/clip/video-clip", () => ({
VideoClip: ({ clip, track }: any) => (
Video Clip
),
}))
vi.mock("../../../components/clip/audio-clip", () => ({
AudioClip: ({ clip, track }: any) => (
Audio Clip
),
}))
vi.mock("../../../components/clip/subtitle-clip", () => ({
SubtitleClip: ({ clip, trackHeight, isSelected }: any) => (
Subtitle Clip
),
}))
// Мокаем компоненты редактирования
vi.mock("../../../components/clip/clip-trim-handles", () => ({
ClipTrimHandles: () => null,
}))
vi.mock("../../../components/edit-tools/slip-slide-handles", () => ({
SlipSlideHandles: () => null,
}))
vi.mock("../../../components/edit-tools/rate-stretch-handle", () => ({
RateStretchHandle: () => null,
}))
describe("Clip", () => {
const mockOnUpdate = vi.fn()
const mockOnRemove = vi.fn()
const baseClip: TimelineClip = {
id: "clip-1",
trackId: "track-1",
mediaFileId: "media-1",
startTime: 5,
duration: 10,
trimStart: 0,
trimEnd: 0,
volume: 1,
isSelected: false,
isLocked: false,
}
const baseTrack: TimelineTrack = {
id: "track-1",
sectionId: "section-1",
type: "video",
name: "Video Track 1",
height: 100,
isExpanded: true,
isLocked: false,
isMuted: false,
clips: [],
}
beforeEach(() => {
vi.clearAllMocks()
})
describe("Позиционирование и размеры", () => {
it("должен правильно вычислять позицию клипа", () => {
render()
const clipElement = screen.getByTestId("timeline-clip")
expect(clipElement).toHaveStyle({ left: "50px" }) // 5 секунд * 10 пикселей/сек
})
it("должен правильно вычислять ширину клипа", () => {
render()
const clipElement = screen.getByTestId("timeline-clip")
expect(clipElement).toHaveStyle({ width: "100px" }) // 10 секунд * 10 пикселей/сек
})
it("должен применять минимальную ширину для маленьких клипов", () => {
const smallClip = { ...baseClip, duration: 0.5 }
render()
const clipElement = screen.getByTestId("timeline-clip")
expect(clipElement).toHaveStyle({ width: "20px" }) // Минимальная ширина
})
it("должен обновлять позицию при изменении timeScale", () => {
const { rerender } = render(
,
)
let clipElement = screen.getByTestId("timeline-clip")
expect(clipElement).toHaveStyle({ left: "50px", width: "100px" })
rerender(
,
)
clipElement = screen.getByTestId("timeline-clip")
expect(clipElement).toHaveStyle({ left: "100px", width: "200px" })
})
})
describe("Рендеринг различных типов клипов", () => {
it("должен рендерить VideoClip для video трека", () => {
render()
expect(screen.getByTestId("video-clip")).toBeInTheDocument()
expect(screen.getByTestId("video-clip")).toHaveAttribute("data-clip-id", "clip-1")
expect(screen.getByTestId("video-clip")).toHaveAttribute("data-track-type", "video")
})
it("должен рендерить VideoClip для image трека", () => {
const imageTrack = { ...baseTrack, type: "image" as const }
render()
expect(screen.getByTestId("video-clip")).toBeInTheDocument()
})
it("должен рендерить AudioClip для audio трека", () => {
const audioTrack = { ...baseTrack, type: "audio" as const }
render()
expect(screen.getByTestId("audio-clip")).toBeInTheDocument()
expect(screen.getByTestId("audio-clip")).toHaveAttribute("data-track-type", "audio")
})
it("должен рендерить AudioClip для различных аудио треков", () => {
const audioTypes = ["music", "voiceover", "sfx", "ambient"] as const
audioTypes.forEach((type) => {
const { unmount } = render(
,
)
expect(screen.getByTestId("audio-clip")).toBeInTheDocument()
expect(screen.getByTestId("audio-clip")).toHaveAttribute("data-track-type", type)
unmount()
})
})
it("должен рендерить SubtitleClip для subtitle трека", () => {
const subtitleTrack = { ...baseTrack, type: "subtitle" as const }
const subtitleClip: SubtitleClip = {
...baseClip,
text: "Test subtitle",
style: {},
}
render(
,
)
expect(screen.getByTestId("subtitle-clip")).toBeInTheDocument()
expect(screen.getByTestId("subtitle-clip")).toHaveAttribute("data-track-height", "100")
expect(screen.getByTestId("subtitle-clip")).toHaveAttribute("data-is-selected", "false")
})
it("должен рендерить SubtitleClip для title трека", () => {
const titleTrack = { ...baseTrack, type: "title" as const }
const titleClip: SubtitleClip = {
...baseClip,
text: "Test title",
style: {},
}
render(
,
)
expect(screen.getByTestId("subtitle-clip")).toBeInTheDocument()
})
it("должен показывать ошибку для невалидного subtitle клипа", () => {
const subtitleTrack = { ...baseTrack, type: "subtitle" as const }
// baseClip не имеет поля text, поэтому не является SubtitleClip
render(
,
)
expect(screen.getByText("Invalid subtitle clip")).toBeInTheDocument()
})
it("должен показывать placeholder для неизвестного типа трека", () => {
const unknownTrack = { ...baseTrack, type: "unknown" as any }
render(
,
)
expect(screen.getByText("unknown")).toBeInTheDocument()
})
})
describe("Состояния клипа", () => {
it("должен применять стили для выделенного клипа", () => {
const selectedClip = { ...baseClip, isSelected: true }
render(
,
)
const clipElement = screen.getByTestId("timeline-clip")
expect(clipElement.className).toMatch(/ring-2/)
expect(clipElement.className).toMatch(/ring-primary/)
})
it("должен применять стили для заблокированного клипа", () => {
const lockedClip = { ...baseClip, isLocked: true }
render(
,
)
const clipElement = screen.getByTestId("timeline-clip")
expect(clipElement.className).toMatch(/opacity-60/)
expect(clipElement.className).toMatch(/cursor-not-allowed/)
})
it("должен передавать isSelected в SubtitleClip", () => {
const subtitleTrack = { ...baseTrack, type: "subtitle" as const }
const selectedSubtitleClip: SubtitleClip = {
...baseClip,
text: "Test",
style: {},
isSelected: true,
}
render(
,
)
expect(screen.getByTestId("subtitle-clip")).toHaveAttribute("data-is-selected", "true")
})
})
describe("Пропсы и классы", () => {
it("должен применять дополнительные классы", () => {
render(
,
)
const clipElement = screen.getByTestId("timeline-clip")
expect(clipElement.className).toMatch(/custom-class/)
})
it("должен передавать callbacks в дочерние компоненты", () => {
render()
// Проверяем что VideoClip получил правильные пропсы
const videoClip = screen.getByTestId("video-clip")
expect(videoClip).toBeInTheDocument()
})
it("должен иметь базовые стили позиционирования", () => {
render()
const clipElement = screen.getByTestId("timeline-clip")
expect(clipElement.className).toMatch(/absolute/)
expect(clipElement.className).toMatch(/top-1/)
expect(clipElement.className).toMatch(/bottom-1/)
expect(clipElement.className).toMatch(/cursor-pointer/)
expect(clipElement.className).toMatch(/transition-all/)
})
})
})