import { render, screen } from "@testing-library/react"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { Separator } from "@/components/ui/separator"
import { SubtitleAITools } from "../../components/subtitle-ai-tools"
import { SubtitleImportButton } from "../../components/subtitle-import-button"
import { SubtitleSyncTools } from "../../components/subtitle-sync-tools"
import { SubtitleToolbar } from "../../components/subtitle-toolbar"
import { SubtitleTools } from "../../components/subtitle-tools"
// Mock timeline hook
vi.mock("@/features/timeline/hooks/use-timeline", () => ({
useTimeline: () => ({
project: { id: "test-project" },
tracks: [],
clips: [],
addTrack: vi.fn(),
addClip: vi.fn(),
addSection: vi.fn(),
}),
}))
// Mock timeline store
vi.mock("@/features/timeline/stores/timeline-store", () => ({
timelineStore: {
addClip: vi.fn(),
addTrack: vi.fn(),
updateClip: vi.fn(),
deleteClip: vi.fn(),
selectClips: vi.fn(),
},
}))
// Mock dependencies
vi.mock("../../components/subtitle-import-button", () => ({
SubtitleImportButton: vi.fn(() =>
Import Button
),
}))
vi.mock("../../components/subtitle-ai-tools", () => ({
SubtitleAITools: vi.fn(() => AI Tools
),
}))
vi.mock("../../components/subtitle-sync-tools", () => ({
SubtitleSyncTools: vi.fn(() => Sync Tools
),
}))
vi.mock("../../components/subtitle-tools", () => ({
SubtitleTools: vi.fn(() => Subtitle Tools
),
}))
vi.mock("@/components/ui/separator", () => ({
Separator: vi.fn(({ orientation, className }) => (
)),
}))
describe("SubtitleToolbar", () => {
beforeEach(() => {
vi.resetAllMocks()
})
describe("rendering", () => {
it("должен рендерить основную структуру тулбара", () => {
const { container } = render()
const toolbar = container.firstChild as HTMLElement
expect(toolbar).toHaveClass("flex")
expect(toolbar).toHaveClass("items-center")
expect(toolbar).toHaveClass("gap-2")
expect(toolbar).toHaveClass("rounded-md")
expect(toolbar).toHaveClass("border")
expect(toolbar).toHaveClass("bg-background")
expect(toolbar).toHaveClass("p-2")
})
it("должен рендерить все компоненты инструментов", () => {
render()
expect(screen.getByTestId("subtitle-import-button")).toBeInTheDocument()
expect(screen.getByTestId("subtitle-tools")).toBeInTheDocument()
expect(screen.getByTestId("subtitle-sync-tools")).toBeInTheDocument()
expect(screen.getByTestId("subtitle-ai-tools")).toBeInTheDocument()
})
it("должен рендерить разделители между инструментами", () => {
render()
const separators = screen.getAllByTestId("separator")
expect(separators).toHaveLength(3)
separators.forEach((separator) => {
expect(separator).toHaveAttribute("data-orientation", "vertical")
expect(separator).toHaveClass("h-6")
})
})
it("должен вызывать компоненты инструментов", () => {
render()
expect(SubtitleImportButton).toHaveBeenCalledTimes(1)
expect(SubtitleTools).toHaveBeenCalledTimes(1)
expect(SubtitleSyncTools).toHaveBeenCalledTimes(1)
expect(SubtitleAITools).toHaveBeenCalledTimes(1)
})
it("должен вызывать Separator с правильными props", () => {
render()
expect(Separator).toHaveBeenCalledTimes(3)
expect(Separator).toHaveBeenCalledWith(
{
orientation: "vertical",
className: "h-6",
},
undefined,
)
})
})
describe("layout", () => {
it("должен располагать компоненты в правильном порядке", () => {
const { container } = render()
const toolbar = container.querySelector(".flex.items-center")!
const children = Array.from(toolbar.children)
expect(children).toHaveLength(7) // 4 компонента + 3 разделителя
// Проверяем порядок элементов
expect(children[0]).toHaveAttribute("data-testid", "subtitle-import-button")
expect(children[1]).toHaveAttribute("data-testid", "separator")
expect(children[2]).toHaveAttribute("data-testid", "subtitle-tools")
expect(children[3]).toHaveAttribute("data-testid", "separator")
expect(children[4]).toHaveAttribute("data-testid", "subtitle-sync-tools")
expect(children[5]).toHaveAttribute("data-testid", "separator")
expect(children[6]).toHaveAttribute("data-testid", "subtitle-ai-tools")
})
it("должен применять flexbox стили для расположения", () => {
const { container } = render()
const toolbar = container.firstChild as HTMLElement
expect(toolbar).toHaveClass("flex")
expect(toolbar).toHaveClass("items-center")
expect(toolbar).toHaveClass("gap-2")
})
it("должен применять стили оформления", () => {
const { container } = render()
const toolbar = container.firstChild as HTMLElement
expect(toolbar).toHaveClass("rounded-md")
expect(toolbar).toHaveClass("border")
expect(toolbar).toHaveClass("bg-background")
expect(toolbar).toHaveClass("p-2")
})
})
describe("component integration", () => {
it("должен корректно интегрировать все подкомпоненты", () => {
const { container } = render()
// Проверяем что все компоненты присутствуют
expect(screen.getByText("Import Button")).toBeInTheDocument()
expect(screen.getByText("Subtitle Tools")).toBeInTheDocument()
expect(screen.getByText("Sync Tools")).toBeInTheDocument()
expect(screen.getByText("AI Tools")).toBeInTheDocument()
// Проверяем что контейнер не пустой
expect(container.firstChild).not.toBeEmptyDOMElement()
})
it("должен сохранять функциональность при повторном рендере", () => {
const { rerender } = render()
expect(SubtitleTools).toHaveBeenCalledTimes(1)
expect(SubtitleSyncTools).toHaveBeenCalledTimes(1)
expect(SubtitleAITools).toHaveBeenCalledTimes(1)
rerender()
expect(SubtitleTools).toHaveBeenCalledTimes(2)
expect(SubtitleSyncTools).toHaveBeenCalledTimes(2)
expect(SubtitleAITools).toHaveBeenCalledTimes(2)
})
})
describe("accessibility", () => {
it("должен создавать доступную структуру", () => {
const { container } = render()
const toolbar = container.querySelector(".flex.items-center")!
expect(toolbar).toBeInTheDocument()
// Проверяем что разделители имеют правильную ориентацию для screen readers
const separators = screen.getAllByTestId("separator")
separators.forEach((separator) => {
expect(separator).toHaveAttribute("data-orientation", "vertical")
})
})
it("должен иметь понятную структуру для assistive technologies", () => {
const { container } = render()
// Проверяем что структура логична и последовательна
const toolbar = container.firstChild as HTMLElement
expect(toolbar.tagName.toLowerCase()).toBe("div")
const children = Array.from(toolbar.children)
expect(children.length).toBeGreaterThan(0)
// Проверяем что каждый ребенок имеет контент или testid
children.forEach((child) => {
const element = child as HTMLElement
expect(
element.textContent || element.getAttribute("data-testid") || element.getAttribute("data-orientation"),
).toBeTruthy()
})
})
})
describe("performance", () => {
it("не должен вызывать лишние перерендеры дочерних компонентов", () => {
const { rerender } = render()
const initialCallCounts = {
import: vi.mocked(SubtitleImportButton).mock.calls.length,
tools: vi.mocked(SubtitleTools).mock.calls.length,
sync: vi.mocked(SubtitleSyncTools).mock.calls.length,
ai: vi.mocked(SubtitleAITools).mock.calls.length,
separator: vi.mocked(Separator).mock.calls.length,
}
// Перерендериваем тот же компонент
rerender()
expect(vi.mocked(SubtitleImportButton).mock.calls.length).toBe(initialCallCounts.import + 1)
expect(vi.mocked(SubtitleTools).mock.calls.length).toBe(initialCallCounts.tools + 1)
expect(vi.mocked(SubtitleSyncTools).mock.calls.length).toBe(initialCallCounts.sync + 1)
expect(vi.mocked(SubtitleAITools).mock.calls.length).toBe(initialCallCounts.ai + 1)
expect(vi.mocked(Separator).mock.calls.length).toBe(initialCallCounts.separator + 3)
})
it("должен быть легковесным без сложной логики", () => {
const startTime = performance.now()
render()
const endTime = performance.now()
// Компонент должен рендериться быстро (менее 50ms)
expect(endTime - startTime).toBeLessThan(50)
})
})
describe("error boundaries", () => {
it("должен корректно отрабатывать при ошибках в дочерних компонентах", () => {
// Мокаем компонент с ошибкой
vi.mocked(SubtitleTools).mockImplementation(() => {
throw new Error("Test error")
})
// Проверяем что ошибка не ломает весь рендер
expect(() => {
render()
}).toThrow("Test error")
})
it("должен рендериться даже если один из дочерних компонентов возвращает null", () => {
vi.mocked(SubtitleTools).mockReturnValue(null as any)
render()
// Остальные компоненты должны отрендериться
expect(screen.getByTestId("subtitle-sync-tools")).toBeInTheDocument()
expect(screen.getByTestId("subtitle-ai-tools")).toBeInTheDocument()
expect(screen.getAllByTestId("separator")).toHaveLength(3)
})
})
})