import { useDroppable } from "@dnd-kit/core" import { screen } from "@testing-library/react" import { beforeEach, describe, expect, it, vi } from "vitest" // Import mocks before components import { renderWithProviders } from "@/test/test-utils" import { mockUseDragDropTimeline } from "../../../__mocks__/hooks" import { TrackContent } from "../../../components/track/track-content" import { TimelineClip, TimelineTrack } from "../../../types" // Import the global mock // Мокаем dnd-kit vi.mock("@dnd-kit/core", () => ({ useDroppable: vi.fn(() => ({ isOver: false, setNodeRef: vi.fn(), })), })) // Мокаем TrackRollHandles компонент vi.mock("../../../components/track/track-roll-handles", () => ({ TrackRollHandles: () => null, })) // Create local references to the mocked drag state and function const mockDragState = { isDragging: false, draggedItem: null, dragOverTrack: null, dropPosition: null, } const mockIsValidDropTarget = vi.fn(() => true) // Мокаем useTimeline const mockAddClip = vi.fn() vi.mock("../../../hooks/use-timeline", () => ({ useTimeline: () => ({ addClip: mockAddClip, project: null, uiState: { selectedClipIds: [], selectedTrackIds: [], selectedSectionIds: [], }, currentTime: 0, error: null, }), })) // Мокаем useDropZone vi.mock("@/features/drag-drop", () => ({ useDropZone: vi.fn(() => ({ ref: { current: null }, })), })) // Мокаем компонент Clip vi.mock("../../../components/clip/clip", () => ({ Clip: ({ clip, timeScale }: any) => (
Clip {clip.id}
), })) // Мокаем плейхэд индикатор vi.mock("../../../components/track/track-playhead-indicator", () => ({ TrackPlayheadIndicator: ({ currentTime, timeScale }: any) => (
Playhead
), })) describe("TrackContent", () => { const mockOnUpdate = vi.fn() const baseTrack: TimelineTrack = { id: "track-1", sectionId: "section-1", type: "video", name: "Video Track 1", height: 100, isLocked: false, isMuted: false, isHidden: false, volume: 1, clips: [], order: 0, isSolo: false, pan: 0, trackEffects: [], trackFilters: [], } const baseClip: TimelineClip = { id: "clip-1", trackId: "track-1", mediaId: "media-1", name: "Test Clip", startTime: 5, duration: 10, offset: 0, mediaStartTime: 0, mediaEndTime: 10, volume: 1, isSelected: false, isLocked: false, isReversed: false, speed: 1, opacity: 1, effects: [], filters: [], transitions: [], createdAt: new Date(), updatedAt: new Date(), } beforeEach(() => { vi.clearAllMocks() mockDragState.isDragging = false mockDragState.dropPosition = null mockIsValidDropTarget.mockReturnValue(true) // Set up the global drag-drop mock to use our local state mockUseDragDropTimeline.mockReturnValue({ dragState: { ...mockDragState, dropPosition: mockDragState.dropPosition as any, }, isValidDropTarget: mockIsValidDropTarget, handleDragStart: vi.fn(), handleDragOver: vi.fn(), handleDragEnd: vi.fn(), isValidDropTargetForNewTrack: vi.fn(() => false), }) // Reset useDroppable mock to default state vi.mocked(useDroppable).mockReturnValue({ isOver: false, setNodeRef: vi.fn(), active: null, rect: { current: null }, node: { current: null }, over: null, } as any) }) describe("Рендеринг основного контента", () => { it("должен рендерить контейнер трека", () => { renderWithProviders() const container = screen.getByTestId("track-container-track-1") expect(container).toBeInTheDocument() }) it("должен рендерить клипы трека", () => { const trackWithClips = { ...baseTrack, clips: [ { ...baseClip, id: "clip-1", startTime: 5 }, { ...baseClip, id: "clip-2", startTime: 20 }, ], } renderWithProviders( , ) expect(screen.getByTestId("clip-clip-1")).toBeInTheDocument() expect(screen.getByTestId("clip-clip-2")).toBeInTheDocument() }) it("должен сортировать клипы по времени начала", () => { const trackWithClips = { ...baseTrack, clips: [ { ...baseClip, id: "clip-2", startTime: 20 }, { ...baseClip, id: "clip-1", startTime: 5 }, { ...baseClip, id: "clip-3", startTime: 15 }, ], } renderWithProviders( , ) const clips = screen.getAllByTestId(/^clip-clip-/) expect(clips[0]).toHaveAttribute("data-start-time", "5") expect(clips[1]).toHaveAttribute("data-start-time", "15") expect(clips[2]).toHaveAttribute("data-start-time", "20") }) it("должен рендерить playhead индикатор", () => { renderWithProviders() // Playhead рендерится как div с определенными классами const container = screen.getByTestId("track-container-track-1") const playhead = container.querySelector(".bg-primary.z-20") expect(playhead).toBeInTheDocument() expect(playhead).toHaveStyle({ left: "50px" }) }) }) describe("Drag and Drop функциональность", () => { it("должен показывать drop feedback при перетаскивании", () => { // Set up the mock to return the correct state before rendering mockUseDragDropTimeline.mockReturnValue({ dragState: { isDragging: true, draggedItem: null, dragOverTrack: null, dropPosition: null, }, isValidDropTarget: vi.fn(() => true), handleDragStart: vi.fn(), handleDragOver: vi.fn(), handleDragEnd: vi.fn(), isValidDropTargetForNewTrack: vi.fn(() => false), }) // Mock useDroppable to return isOver: true vi.mocked(useDroppable).mockReturnValue({ isOver: true, setNodeRef: vi.fn(), active: null, rect: { current: null }, node: { current: null }, over: null, } as any) renderWithProviders() expect(screen.getByText("Drop here")).toBeInTheDocument() }) it("должен применять стили для валидного drop target", () => { mockUseDragDropTimeline.mockReturnValue({ dragState: { isDragging: true, draggedItem: null, dragOverTrack: null, dropPosition: null, }, isValidDropTarget: vi.fn(() => true), handleDragStart: vi.fn(), handleDragOver: vi.fn(), handleDragEnd: vi.fn(), isValidDropTargetForNewTrack: vi.fn(() => false), }) renderWithProviders() const container = screen.getByTestId("track-container-track-1") expect(container.className).toMatch(/border-dashed/) expect(container.className).toMatch(/border-2/) }) it("должен применять стили для невалидного drop target", () => { mockUseDragDropTimeline.mockReturnValue({ dragState: { isDragging: true, draggedItem: null, dragOverTrack: null, dropPosition: null, }, isValidDropTarget: vi.fn(() => false), handleDragStart: vi.fn(), handleDragOver: vi.fn(), handleDragEnd: vi.fn(), isValidDropTargetForNewTrack: vi.fn(() => false), }) renderWithProviders() const container = screen.getByTestId("track-container-track-1") expect(container.className).toMatch(/opacity-50/) }) it("должен показывать insertion индикатор", () => { mockUseDragDropTimeline.mockReturnValue({ dragState: { isDragging: false, draggedItem: null, dragOverTrack: null, dropPosition: { trackId: "track-1", startTime: 10, }, }, isValidDropTarget: vi.fn(() => true), handleDragStart: vi.fn(), handleDragOver: vi.fn(), handleDragEnd: vi.fn(), isValidDropTargetForNewTrack: vi.fn(() => false), }) renderWithProviders() const indicator = document.querySelector(".bg-primary.z-25") expect(indicator).toBeInTheDocument() expect(indicator).toHaveStyle({ left: "100px" }) // 10 * 10 }) it("не должен показывать insertion индикатор для другого трека", () => { mockUseDragDropTimeline.mockReturnValue({ dragState: { isDragging: false, draggedItem: null, dragOverTrack: null, dropPosition: { trackId: "track-2", // Другой трек startTime: 10, }, }, isValidDropTarget: vi.fn(() => true), handleDragStart: vi.fn(), handleDragOver: vi.fn(), handleDragEnd: vi.fn(), isValidDropTargetForNewTrack: vi.fn(() => false), }) renderWithProviders() const indicator = document.querySelector(".bg-primary.z-25") expect(indicator).not.toBeInTheDocument() }) }) describe("Обработка событий клипов", () => { it("должен обновлять клип через onUpdate", () => { const trackWithClip = { ...baseTrack, clips: [baseClip], } renderWithProviders() // Симулируем вызов handleClipUpdate // В реальном компоненте это происходит через пропс onUpdate компонента Clip const clipElement = screen.getByTestId("clip-clip-1") expect(clipElement).toBeInTheDocument() // Проверяем что onUpdate передан в правильном формате expect(mockOnUpdate).not.toHaveBeenCalled() }) it("должен удалять клип через onUpdate", () => { const trackWithClips = { ...baseTrack, clips: [ { ...baseClip, id: "clip-1" }, { ...baseClip, id: "clip-2", startTime: 20 }, ], } renderWithProviders( , ) // В реальном компоненте удаление происходит через пропс onRemove компонента Clip expect(screen.getByTestId("clip-clip-1")).toBeInTheDocument() expect(screen.getByTestId("clip-clip-2")).toBeInTheDocument() }) }) describe("Стили и классы", () => { it("должен иметь базовые стили", () => { renderWithProviders() const container = screen.getByTestId("track-container-track-1") expect(container.className).toMatch(/relative/) expect(container.className).toMatch(/h-full/) expect(container.className).toMatch(/w-full/) expect(container.className).toMatch(/bg-background/) expect(container.className).toMatch(/border-l/) expect(container.className).toMatch(/border-border/) }) it("должен показывать drop zone визуальный feedback", () => { mockUseDragDropTimeline.mockReturnValue({ dragState: { isDragging: true, draggedItem: null, dragOverTrack: null, dropPosition: null, }, isValidDropTarget: vi.fn(() => true), handleDragStart: vi.fn(), handleDragOver: vi.fn(), handleDragEnd: vi.fn(), isValidDropTargetForNewTrack: vi.fn(() => false), }) vi.mocked(useDroppable).mockReturnValue({ isOver: true, setNodeRef: vi.fn(), active: null, rect: { current: null }, node: { current: null }, over: null, } as any) renderWithProviders() const dropZone = screen.getByText("Drop here").closest(".absolute") expect(dropZone).toHaveClass("absolute") expect(dropZone).toHaveClass("inset-0") expect(dropZone).toHaveClass("bg-primary/5") }) }) describe("Работа с различными типами треков", () => { it("должен корректно работать с audio треком", () => { const audioTrack = { ...baseTrack, type: "audio" as const } renderWithProviders() expect(screen.getByTestId("track-container-track-1")).toBeInTheDocument() }) it("должен корректно работать с subtitle треком", () => { const subtitleTrack = { ...baseTrack, type: "subtitle" as const } renderWithProviders() expect(screen.getByTestId("track-container-track-1")).toBeInTheDocument() }) }) })