import { beforeEach, describe, expect, it, vi } from "vitest"
import { render, screen } from "@/test/test-utils"
import { EditCursorOverlay, EditModeOverlay } from "../../../components/edit-tools/edit-mode-overlay"
import { EDIT_MODES } from "../../../types/edit-modes"
// Мокаем useEditModeContext
const mockEditModeContext = {
editMode: EDIT_MODES.SELECT as any,
setEditMode: vi.fn(),
isEditMode: vi.fn(),
cursor: "default",
}
vi.mock("../../../hooks/use-edit-mode", () => ({
useEditModeContext: () => mockEditModeContext,
}))
// Мокаем cn utility
vi.mock("@/lib/utils", () => ({
cn: (...classes: any[]) => classes.filter(Boolean).join(" "),
}))
describe("EditModeOverlay", () => {
beforeEach(() => {
vi.clearAllMocks()
// Сбрасываем состояние мока к дефолтному
mockEditModeContext.editMode = EDIT_MODES.SELECT
})
describe("Базовый рендеринг", () => {
it("должен не отображаться в SELECT режиме", () => {
mockEditModeContext.editMode = EDIT_MODES.SELECT
const { container } = render()
// В SELECT режиме компонент не должен рендерить никакой контент
expect(container.querySelector('[class*="fixed"]')).not.toBeInTheDocument()
})
it("должен отображаться в TRIM режиме", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
render()
expect(screen.getByText("Trim Mode")).toBeInTheDocument()
expect(screen.getByText("Trim clip edges")).toBeInTheDocument()
expect(screen.getByText("T")).toBeInTheDocument() // hotkey
expect(screen.getByText("ESC")).toBeInTheDocument()
})
it("должен отображаться в RIPPLE режиме", () => {
mockEditModeContext.editMode = EDIT_MODES.RIPPLE
render()
expect(screen.getByText("Ripple Mode")).toBeInTheDocument()
expect(screen.getByText("Ripple edit - trim and move subsequent clips")).toBeInTheDocument()
expect(screen.getByText("Q")).toBeInTheDocument() // hotkey
})
it("должен отображаться в ROLL режиме", () => {
mockEditModeContext.editMode = EDIT_MODES.ROLL
render()
expect(screen.getByText("Roll Mode")).toBeInTheDocument()
expect(screen.getByText("Roll edit - adjust edit point between clips")).toBeInTheDocument()
expect(screen.getByText("W")).toBeInTheDocument() // hotkey
})
it("должен отображаться в SLIP режиме", () => {
mockEditModeContext.editMode = EDIT_MODES.SLIP
render()
expect(screen.getByText("Slip Mode")).toBeInTheDocument()
expect(screen.getByText("Slip edit - change clip content without moving")).toBeInTheDocument()
expect(screen.getByText("Y")).toBeInTheDocument() // hotkey
})
it("должен отображаться в SLIDE режиме", () => {
mockEditModeContext.editMode = EDIT_MODES.SLIDE
render()
expect(screen.getByText("Slide Mode")).toBeInTheDocument()
expect(screen.getByText("Slide edit - move clip and adjust neighbors")).toBeInTheDocument()
expect(screen.getByText("U")).toBeInTheDocument() // hotkey
})
it("должен отображаться в SPLIT режиме", () => {
mockEditModeContext.editMode = EDIT_MODES.SPLIT
render()
expect(screen.getByText("Split Mode")).toBeInTheDocument()
expect(screen.getByText("Split clips at cursor position")).toBeInTheDocument()
expect(screen.getByText("S")).toBeInTheDocument() // hotkey
})
it("должен отображаться в RATE режиме", () => {
mockEditModeContext.editMode = EDIT_MODES.RATE
render()
expect(screen.getByText("Rate Mode")).toBeInTheDocument()
expect(screen.getByText("Change clip playback speed")).toBeInTheDocument()
expect(screen.getByText("R")).toBeInTheDocument() // hotkey
})
})
describe("Стили и классы", () => {
it("должен применять базовые стили", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
// Находим overlay по классам
const overlay = container.querySelector('[class*="fixed"][class*="top-20"]')
expect(overlay).toBeInTheDocument()
expect(overlay).toHaveClass("fixed")
expect(overlay).toHaveClass("top-20")
expect(overlay).toHaveClass("left-1/2")
expect(overlay).toHaveClass("-translate-x-1/2")
expect(overlay).toHaveClass("z-50")
expect(overlay).toHaveClass("px-4")
expect(overlay).toHaveClass("py-2")
expect(overlay).toHaveClass("rounded-lg")
})
it("должен применять кастомный className", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
const overlay = container.querySelector('[class*="fixed"][class*="top-20"]')
expect(overlay).toBeInTheDocument()
expect(overlay).toHaveClass("custom-class")
})
it("должен иметь pointer-events-none", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
const overlay = container.querySelector('[class*="fixed"][class*="top-20"]')
expect(overlay).toBeInTheDocument()
expect(overlay).toHaveClass("pointer-events-none")
expect(overlay).toHaveClass("select-none")
})
it("должен содержать правильную структуру элементов", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
// Проверяем наличие всех основных элементов
expect(container.querySelector(".flex.items-center.gap-3")).toBeInTheDocument()
expect(container.querySelector(".w-10.h-10.rounded-md")).toBeInTheDocument()
expect(container.querySelector(".font-semibold")).toBeInTheDocument()
expect(container.querySelector("kbd")).toBeInTheDocument()
})
})
describe("Hotkey отображение", () => {
it("должен отображать правильный hotkey для каждого режима", () => {
const modes = [
{ mode: EDIT_MODES.TRIM, hotkey: "T" },
{ mode: EDIT_MODES.RIPPLE, hotkey: "Q" },
{ mode: EDIT_MODES.ROLL, hotkey: "W" },
{ mode: EDIT_MODES.SLIP, hotkey: "Y" },
{ mode: EDIT_MODES.SLIDE, hotkey: "U" },
{ mode: EDIT_MODES.SPLIT, hotkey: "S" },
{ mode: EDIT_MODES.RATE, hotkey: "R" },
]
modes.forEach(({ mode, hotkey }) => {
mockEditModeContext.editMode = mode
render()
expect(screen.getByText(hotkey)).toBeInTheDocument()
// Очищаем экран между итерациями
screen.debug = vi.fn()
})
})
})
})
describe("EditCursorOverlay", () => {
beforeEach(() => {
vi.clearAllMocks()
mockEditModeContext.editMode = EDIT_MODES.TRIM
})
describe("Видимость курсора", () => {
it("должен не отображаться когда isActive = false", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
expect(container.querySelector('[class*="fixed"][class*="pointer-events-none"]')).not.toBeInTheDocument()
})
it("должен не отображаться когда mousePosition = null", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
expect(container.querySelector('[class*="fixed"][class*="pointer-events-none"]')).not.toBeInTheDocument()
})
it("должен не отображаться в SELECT режиме", () => {
mockEditModeContext.editMode = EDIT_MODES.SELECT
const { container } = render()
expect(container.querySelector('[class*="fixed"][class*="pointer-events-none"]')).not.toBeInTheDocument()
})
it("должен отображаться когда isActive = true и есть mousePosition", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
expect(container.firstChild).toBeInTheDocument()
})
})
describe("Позиционирование курсора", () => {
it("должен позиционироваться в правильной позиции мыши", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
const overlay = container.querySelector('[class*="fixed"][class*="pointer-events-none"]')!
expect(overlay).toBeInTheDocument()
expect(overlay).toHaveStyle({
left: "150px",
top: "200px",
transform: "translate(-50%, -50%)",
})
})
it("должен иметь правильные классы позиционирования", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
const overlay = container.querySelector('[class*="fixed"][class*="pointer-events-none"]')!
expect(overlay).toBeInTheDocument()
expect(overlay).toHaveClass("fixed")
expect(overlay).toHaveClass("pointer-events-none")
expect(overlay).toHaveClass("z-[100]")
})
})
describe("Курсоры для разных режимов", () => {
it("должен показывать TrimCursor для TRIM режима", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
// Проверяем наличие SVG элемента
const svg = container.querySelector("svg")
expect(svg).toBeInTheDocument()
expect(svg).toHaveClass("text-primary")
})
it("должен показывать RippleCursor для RIPPLE режима", () => {
mockEditModeContext.editMode = EDIT_MODES.RIPPLE
const { container } = render()
const svg = container.querySelector("svg")
expect(svg).toBeInTheDocument()
expect(svg).toHaveClass("text-orange-500")
})
it("должен показывать RollCursor для ROLL режима", () => {
mockEditModeContext.editMode = EDIT_MODES.ROLL
const { container } = render()
const svg = container.querySelector("svg")
expect(svg).toBeInTheDocument()
expect(svg).toHaveClass("text-purple-500")
})
it("должен показывать SlipCursor для SLIP режима", () => {
mockEditModeContext.editMode = EDIT_MODES.SLIP
const { container } = render()
const svg = container.querySelector("svg")
expect(svg).toBeInTheDocument()
expect(svg).toHaveClass("text-blue-500")
})
it("должен показывать SlideCursor для SLIDE режима", () => {
mockEditModeContext.editMode = EDIT_MODES.SLIDE
const { container } = render()
const svg = container.querySelector("svg")
expect(svg).toBeInTheDocument()
expect(svg).toHaveClass("text-green-500")
})
it("должен показывать SplitCursor для SPLIT режима", () => {
mockEditModeContext.editMode = EDIT_MODES.SPLIT
const { container } = render()
const svg = container.querySelector("svg")
expect(svg).toBeInTheDocument()
expect(svg).toHaveClass("text-red-500")
})
it("должен показывать RateCursor для RATE режима", () => {
mockEditModeContext.editMode = EDIT_MODES.RATE
const { container } = render()
const svg = container.querySelector("svg")
expect(svg).toBeInTheDocument()
expect(svg).toHaveClass("text-yellow-500")
// Проверяем наличие текста "2x" в RateCursor
const text = container.querySelector("text")
expect(text).toBeInTheDocument()
expect(text).toHaveTextContent("2x")
})
})
describe("Edge cases", () => {
it("должен корректно обрабатывать нулевые координаты", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
const overlay = container.querySelector('[class*="fixed"][class*="pointer-events-none"]')!
expect(overlay).toBeInTheDocument()
expect(overlay).toHaveStyle({
left: "0px",
top: "0px",
})
})
it("должен корректно обрабатывать большие координаты", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
const overlay = container.querySelector('[class*="fixed"][class*="pointer-events-none"]')!
expect(overlay).toBeInTheDocument()
expect(overlay).toHaveStyle({
left: "9999px",
top: "9999px",
})
})
it("должен корректно обрабатывать отрицательные координаты", () => {
mockEditModeContext.editMode = EDIT_MODES.TRIM
const { container } = render()
const overlay = container.querySelector('[class*="fixed"][class*="pointer-events-none"]')!
expect(overlay).toBeInTheDocument()
expect(overlay).toHaveStyle({
left: "-100px",
top: "-50px",
})
})
})
describe("SVG элементы курсоров", () => {
it("все курсоры должны иметь правильные размеры SVG", () => {
const modes = [
EDIT_MODES.TRIM,
EDIT_MODES.RIPPLE,
EDIT_MODES.ROLL,
EDIT_MODES.SLIP,
EDIT_MODES.SLIDE,
EDIT_MODES.SPLIT,
EDIT_MODES.RATE,
]
modes.forEach((mode) => {
mockEditModeContext.editMode = mode
const { container } = render()
const svg = container.querySelector("svg")
expect(svg).toBeInTheDocument()
expect(svg).toHaveAttribute("width", "32")
expect(svg).toHaveAttribute("height", "32")
expect(svg).toHaveAttribute("viewBox", "0 0 32 32")
})
})
it("все курсоры должны содержать графические элементы", () => {
const modes = [
EDIT_MODES.TRIM,
EDIT_MODES.RIPPLE,
EDIT_MODES.ROLL,
EDIT_MODES.SLIP,
EDIT_MODES.SLIDE,
EDIT_MODES.SPLIT,
EDIT_MODES.RATE,
]
modes.forEach((mode) => {
mockEditModeContext.editMode = mode
const { container } = render()
const svg = container.querySelector("svg")
expect(svg).toBeInTheDocument()
// Каждый курсор должен содержать хотя бы один графический элемент
const graphicsElements = svg!.querySelectorAll("path, circle, rect, text")
expect(graphicsElements.length).toBeGreaterThan(0)
})
})
})
})