import { invoke } from "@tauri-apps/api/core"
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react"
import { vi } from "vitest"
import { RenderJob, RenderJobsDropdown, RenderStatus } from "@/features/video-compiler"
import { setTranslations } from "@/test/mocks/libraries/i18n"
// Mock the Tauri API
vi.mock("@tauri-apps/api/core", () => ({
invoke: vi.fn(),
}))
const mockJobs: RenderJob[] = [
{
id: "job-1",
project_name: "Test Video 1",
output_path: "/output/test1.mp4",
status: RenderStatus.Processing,
created_at: new Date().toISOString(),
progress: {
job_id: "job-1",
stage: "encoding",
percentage: 50,
current_frame: 500,
total_frames: 1000,
elapsed_time: 120000,
status: RenderStatus.Processing,
message: "Encoding video...",
},
},
{
id: "job-2",
project_name: "Test Video 2",
output_path: "/output/test2.mp4",
status: RenderStatus.Completed,
created_at: new Date(Date.now() - 300000).toISOString(),
progress: {
job_id: "job-2",
stage: "completed",
percentage: 100,
current_frame: 500,
total_frames: 500,
elapsed_time: 300000,
status: RenderStatus.Completed,
},
},
{
id: "job-3",
project_name: "Test Video 3",
output_path: "/output/test3.mp4",
status: RenderStatus.Failed,
created_at: new Date(Date.now() - 600000).toISOString(),
error_message: "FFmpeg encoding error",
progress: {
job_id: "job-3",
stage: "failed",
percentage: 25,
current_frame: 187,
total_frames: 750,
elapsed_time: 60000,
status: RenderStatus.Failed,
message: "FFmpeg encoding error",
},
},
]
describe("RenderJobsDropdown", () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(invoke).mockResolvedValue(mockJobs)
// Set up translations for our tests
setTranslations({
"videoCompiler.tasks": "Задачи",
"videoCompiler.renderTasks": "Задачи рендеринга",
"videoCompiler.noActiveTasks": "Нет активных задач",
"videoCompiler.errorLoadingTasks": "Ошибка загрузки задач",
"videoCompiler.cancelTask": "Отменить задачу рендеринга?",
"videoCompiler.frames": "кадров",
"videoCompiler.totalTasks": "Всего задач",
"videoCompiler.activeTasks": "Активных",
"videoCompiler.completedTasks": "Завершено",
"videoCompiler.status.pending": "В очереди",
"videoCompiler.status.processing": "Обработка",
"videoCompiler.status.completed": "Завершено",
"videoCompiler.status.failed": "Ошибка",
"videoCompiler.status.cancelled": "Отменено",
})
})
afterEach(() => {
vi.clearAllTimers()
})
it("should render the dropdown button with correct text", () => {
render()
expect(screen.getByText("Задачи")).toBeInTheDocument()
})
it("should show active jobs count badge", async () => {
render()
await waitFor(
() => {
expect(screen.getByText("1")).toBeInTheDocument() // 1 active job (Processing)
},
{ timeout: 3000 },
)
})
it("should open dropdown when clicked", async () => {
render()
const button = screen.getByRole("button", { name: /задачи/i })
// Wait for initial load
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("get_active_jobs")
})
await act(async () => {
fireEvent.click(button)
})
// Check if dropdown content is rendered
await waitFor(
() => {
const dropdownContent = screen.getByTestId("dropdown-menu-content")
expect(dropdownContent).toBeInTheDocument()
expect(within(dropdownContent).getByText("Задачи рендеринга")).toBeInTheDocument()
},
{ timeout: 3000 },
)
})
it("should display all jobs in the dropdown", async () => {
render()
await waitFor(() => {
expect(screen.getByRole("button", { name: /задачи/i })).toBeInTheDocument()
})
const button = screen.getByRole("button", { name: /задачи/i })
await act(async () => {
fireEvent.click(button)
})
const dropdownContent = await screen.findByTestId("dropdown-menu-content")
await waitFor(
() => {
expect(within(dropdownContent).getByText("Test Video 1")).toBeInTheDocument()
expect(within(dropdownContent).getByText("Test Video 2")).toBeInTheDocument()
expect(within(dropdownContent).getByText("Test Video 3")).toBeInTheDocument()
},
{ timeout: 3000 },
)
})
it("should show progress for processing jobs", async () => {
render()
await waitFor(() => {
expect(screen.getByRole("button", { name: /задачи/i })).toBeInTheDocument()
})
const button = screen.getByRole("button", { name: /задачи/i })
await act(async () => {
fireEvent.click(button)
})
const dropdownContent = await screen.findByTestId("dropdown-menu-content")
await waitFor(
() => {
expect(within(dropdownContent).getByText("Encoding video...")).toBeInTheDocument()
expect(within(dropdownContent).getByText("500/1000 кадров")).toBeInTheDocument()
},
{ timeout: 3000 },
)
})
it("should show error message for failed jobs", async () => {
render()
await waitFor(() => {
expect(screen.getByRole("button", { name: /задачи/i })).toBeInTheDocument()
})
const button = screen.getByRole("button", { name: /задачи/i })
await act(async () => {
fireEvent.click(button)
})
const dropdownContent = await screen.findByTestId("dropdown-menu-content")
await waitFor(
() => {
expect(within(dropdownContent).getByText("FFmpeg encoding error")).toBeInTheDocument()
},
{ timeout: 3000 },
)
})
it("should handle cancel job action", async () => {
const mockConfirm = vi.spyOn(window, "confirm").mockReturnValue(true)
vi.mocked(invoke).mockImplementation((cmd) => {
if (cmd === "cancel_render") return Promise.resolve(true)
return Promise.resolve(mockJobs)
})
render()
await waitFor(() => {
expect(screen.getByRole("button", { name: /задачи/i })).toBeInTheDocument()
})
const button = screen.getByRole("button", { name: /задачи/i })
await act(async () => {
fireEvent.click(button)
})
await waitFor(() => {
const cancelButtons = screen.getAllByRole("button").filter((btn) => btn.querySelector("svg"))
expect(cancelButtons.length).toBeGreaterThan(1)
})
const cancelButtons = screen.getAllByRole("button").filter((btn) => btn.querySelector("svg"))
const processingJobCancelButton = cancelButtons[cancelButtons.length - 1]
await act(async () => {
fireEvent.click(processingJobCancelButton)
})
expect(mockConfirm).toHaveBeenCalledWith("Отменить задачу рендеринга?")
expect(invoke).toHaveBeenCalledWith("cancel_render", { jobId: "job-1" })
mockConfirm.mockRestore()
})
it("should show empty state when no jobs", async () => {
vi.mocked(invoke).mockResolvedValue([])
render()
await waitFor(() => {
expect(screen.getByRole("button", { name: /задачи/i })).toBeInTheDocument()
})
const button = screen.getByRole("button", { name: /задачи/i })
await act(async () => {
fireEvent.click(button)
})
const dropdownContent = await screen.findByTestId("dropdown-menu-content")
await waitFor(
() => {
expect(within(dropdownContent).getByText("Нет активных задач")).toBeInTheDocument()
},
{ timeout: 3000 },
)
})
it("should handle error state", async () => {
vi.mocked(invoke).mockRejectedValue(new Error("Failed to fetch jobs"))
render()
await waitFor(() => {
expect(screen.getByRole("button", { name: /задачи/i })).toBeInTheDocument()
})
const button = screen.getByRole("button", { name: /задачи/i })
await act(async () => {
fireEvent.click(button)
})
const dropdownContent = await screen.findByTestId("dropdown-menu-content")
await waitFor(
() => {
expect(within(dropdownContent).getByText("Ошибка загрузки задач")).toBeInTheDocument()
},
{ timeout: 3000 },
)
})
it("should refresh jobs periodically", async () => {
vi.useFakeTimers({ shouldAdvanceTime: true })
render()
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("get_active_jobs")
})
const initialCallCount = vi.mocked(invoke).mock.calls.length
// Fast-forward 2 seconds
act(() => {
vi.advanceTimersByTime(2000)
})
// Give React time to process the timer
await act(async () => {
await Promise.resolve()
})
expect(vi.mocked(invoke).mock.calls.length).toBeGreaterThan(initialCallCount)
vi.useRealTimers()
})
it("should display job statistics", async () => {
render()
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("get_active_jobs")
})
const button = screen.getByRole("button", { name: /задачи/i })
await act(async () => {
fireEvent.click(button)
})
const dropdownContent = await screen.findByTestId("dropdown-menu-content")
// Check that statistics section exists
const statsSection = within(dropdownContent).getByText("Всего задач:").closest("div")?.parentElement
expect(statsSection).toBeInTheDocument()
// Check statistics text content
expect(within(dropdownContent).getByText("Всего задач:")).toBeInTheDocument()
expect(within(dropdownContent).getByText("3")).toBeInTheDocument()
expect(within(dropdownContent).getByText("Активных:")).toBeInTheDocument()
expect(within(dropdownContent).getByText("Завершено:")).toBeInTheDocument()
// Verify the statistics section contains the correct numbers
const statsText = statsSection?.textContent || ""
expect(statsText).toContain("Всего задач:3")
expect(statsText).toContain("Активных:1")
expect(statsText).toContain("Завершено:1")
})
})