import { fireEvent, screen, waitFor } from "@testing-library/react" import { beforeEach, describe, expect, it, vi } from "vitest" import { render } from "@/test/test-utils" import { SubtitleSyncTools } from "../../components/subtitle-sync-tools" const mockUpdateClip = vi.fn() const mockToast = vi.fn() vi.mock("@/features/timeline/hooks/use-timeline", () => ({ useTimeline: () => ({ updateClip: mockUpdateClip, }), })) vi.mock("@/features/timeline/hooks/use-tracks", () => ({ useTracks: () => ({ tracks: [ { id: "subtitle-track-1", type: "subtitle", clips: [ { id: "sub-1", trackId: "subtitle-track-1", type: "subtitle", startTime: 1, duration: 2, text: "First subtitle", }, { id: "sub-2", trackId: "subtitle-track-1", type: "subtitle", startTime: 4, duration: 2, text: "Second subtitle", }, ], }, ], }), })) vi.mock("@/hooks/use-toast", () => ({ useToast: () => ({ toast: mockToast, }), })) vi.mock("react-i18next", async () => { const actual = await vi.importActual("react-i18next") return { ...actual, useTranslation: () => ({ t: (key: string, fallback?: string, options?: any) => { if (options?.count) { return fallback?.replace("{{count}}", options.count.toString()) || key } if (options?.time) { return fallback?.replace("{{time}}", options.time.toString()) || key } return fallback || key }, }), } }) describe("SubtitleSyncTools", () => { beforeEach(() => { vi.clearAllMocks() }) it("should render sync button", () => { render() expect(screen.getByText("Синхронизация")).toBeInTheDocument() }) it("should open popover when clicked", () => { render() const syncButton = screen.getByText("Синхронизация") fireEvent.click(syncButton) expect(screen.getByText("Настройка времени")).toBeInTheDocument() expect(screen.getByLabelText("Временной сдвиг (секунды)")).toBeInTheDocument() }) it("should update time offset with input", () => { render() const syncButton = screen.getByText("Синхронизация") fireEvent.click(syncButton) const input = screen.getByLabelText("Временной сдвиг (секунды)") fireEvent.change(input, { target: { value: "2.5" } }) expect(input).toHaveValue(2.5) }) it("should increase time offset with plus button", () => { render() const syncButton = screen.getByText("Синхронизация") fireEvent.click(syncButton) const plusButton = screen.getAllByRole("button")[2] // Third button is plus fireEvent.click(plusButton) const input = screen.getByLabelText("Временной сдвиг (секунды)") expect(input).toHaveValue(0.1) }) it("should decrease time offset with minus button", () => { render() const syncButton = screen.getByText("Синхронизация") fireEvent.click(syncButton) const minusButton = screen.getAllByRole("button")[1] // Second button is minus fireEvent.click(minusButton) const input = screen.getByLabelText("Временной сдвиг (секунды)") expect(input).toHaveValue(-0.1) }) it("should reset time offset", () => { render() const syncButton = screen.getByText("Синхронизация") fireEvent.click(syncButton) // Set offset const input = screen.getByLabelText("Временной сдвиг (секунды)") fireEvent.change(input, { target: { value: "5" } }) // Reset const resetButton = screen.getAllByRole("button")[3] // Fourth button is reset fireEvent.click(resetButton) expect(input).toHaveValue(0) }) it("should apply time offset to subtitles", async () => { mockUpdateClip.mockResolvedValue(undefined) render() const syncButton = screen.getByText("Синхронизация") fireEvent.click(syncButton) const input = screen.getByLabelText("Временной сдвиг (секунды)") fireEvent.change(input, { target: { value: "2" } }) const applyButton = screen.getByText("Применить") fireEvent.click(applyButton) await waitFor(() => { expect(mockUpdateClip).toHaveBeenCalledTimes(2) expect(mockUpdateClip).toHaveBeenCalledWith("sub-1", { startTime: 3, // 1 + 2 }) expect(mockUpdateClip).toHaveBeenCalledWith("sub-2", { startTime: 6, // 4 + 2 }) }) // Toast вызывается через console.log заглушку, что видно в логах тестов }) it("should not allow negative start times", async () => { mockUpdateClip.mockResolvedValue(undefined) render() const syncButton = screen.getByText("Синхронизация") fireEvent.click(syncButton) const input = screen.getByLabelText("Временной сдвиг (секунды)") fireEvent.change(input, { target: { value: "-2" } }) const applyButton = screen.getByText("Применить") fireEvent.click(applyButton) await waitFor(() => { expect(mockUpdateClip).toHaveBeenCalledWith("sub-1", { startTime: 0, // Max(0, 1 - 2) = 0 }) }) }) it("should disable apply button when offset is 0", () => { render() const syncButton = screen.getByText("Синхронизация") fireEvent.click(syncButton) const applyButton = screen.getByText("Применить") expect(applyButton).toBeDisabled() }) it("should show appropriate message based on offset", () => { render() const syncButton = screen.getByText("Синхронизация") fireEvent.click(syncButton) // No shift expect(screen.getByText("Субтитры не будут сдвинуты")).toBeInTheDocument() // Positive shift const input = screen.getByLabelText("Временной сдвиг (секунды)") fireEvent.change(input, { target: { value: "2.5" } }) expect(screen.getByText("Субтитры будут сдвинуты вперед на 2.5с")).toBeInTheDocument() // Negative shift fireEvent.change(input, { target: { value: "-1.5" } }) expect(screen.getByText("Субтитры будут сдвинуты назад на 1.5с")).toBeInTheDocument() }) })