/**
* Tests for GenerationWizard component
*/
import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { createMockAnalysis, createMockGeneratedScript } from "../../../hooks/__tests__/test-utils"
import { useAIIntelligence } from "../../../hooks/use-ai-intelligence"
import { GenerationWizard } from "../generation-wizard"
// Mock the hooks
vi.mock("../../../hooks/use-ai-intelligence", () => ({
useAIIntelligence: vi.fn(),
}))
// Default mock implementations
const mockGenerateScript = vi.fn()
const defaultAIIntelligenceMock = {
isProcessing: false,
progress: null,
error: null,
result: null,
generateScript: mockGenerateScript,
}
describe("GenerationWizard", () => {
const user = userEvent.setup()
const mockAnalysis = createMockAnalysis()
const mockOnGenerate = vi.fn()
const mockOnCancel = vi.fn()
const mockOnClose = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(useAIIntelligence).mockReturnValue(defaultAIIntelligenceMock)
})
describe("Basic Rendering", () => {
it("should render wizard with template step", () => {
render()
expect(screen.getByText("Мастер генерации скрипта")).toBeInTheDocument()
expect(screen.getByText("Шаг 1 из 6")).toBeInTheDocument()
expect(screen.getByText("Выбор шаблона")).toBeInTheDocument()
})
it("should apply custom className", () => {
const customClass = "custom-generation-wizard"
const { container } = render()
const element = container.firstElementChild
expect(element).toHaveClass(customClass)
expect(element).toHaveClass("generation-wizard")
})
it("should show all template options", () => {
render()
expect(screen.getByText("Кинематографический рассказ")).toBeInTheDocument()
expect(screen.getByText("Документальный")).toBeInTheDocument()
expect(screen.getByText("Социальные сети")).toBeInTheDocument()
expect(screen.getByText("Коммерческий")).toBeInTheDocument()
expect(screen.getByText("Видеоблог")).toBeInTheDocument()
})
it("should start with next button disabled", () => {
render()
const nextButton = screen.getByRole("button", { name: /далее/i })
expect(nextButton).toBeDisabled()
})
})
describe("Template Selection", () => {
it("should enable next button when template is selected", async () => {
render()
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
expect(cinematicCard).toBeInTheDocument()
await user.click(cinematicCard!)
const nextButton = screen.getByRole("button", { name: /далее/i })
expect(nextButton).not.toBeDisabled()
})
it("should show template description when selected", async () => {
render()
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
expect(screen.getByText(/трёхактный фильм с драматической структурой/i)).toBeInTheDocument()
})
it("should allow deselecting template", async () => {
render()
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
await user.click(cinematicCard!) // Click again to deselect
const nextButton = screen.getByRole("button", { name: /далее/i })
expect(nextButton).toBeDisabled()
})
})
describe("Wizard Navigation", () => {
it("should navigate to style step after template selection", async () => {
render()
// Select template
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
// Click next
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
expect(screen.getByText("Шаг 2 из 6")).toBeInTheDocument()
expect(screen.getByText("Стиль и жанр")).toBeInTheDocument()
})
it("should navigate back to previous step", async () => {
render()
// Navigate to step 2
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
// Go back
const backButton = screen.getByRole("button", { name: /назад/i })
await user.click(backButton)
expect(screen.getByText("Шаг 1 из 6")).toBeInTheDocument()
expect(screen.getByText("Выбор шаблона")).toBeInTheDocument()
})
it("should disable back button on first step", () => {
render()
const backButton = screen.getByRole("button", { name: /назад/i })
expect(backButton).toBeDisabled()
})
it("should show cancel button on all steps", () => {
render()
expect(screen.getByRole("button", { name: /отмена/i })).toBeInTheDocument()
})
})
describe("Style and Genre Step", () => {
beforeEach(async () => {
render()
// Navigate to style step
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
})
it("should show style configuration options", () => {
expect(screen.getByText("Визуальный стиль")).toBeInTheDocument()
expect(screen.getByText("Жанр")).toBeInTheDocument()
expect(screen.getByText("Целевая аудитория")).toBeInTheDocument()
expect(screen.getByText("Эмоциональный тон")).toBeInTheDocument()
})
it("should allow changing visual style", async () => {
const visualStyleSelect = screen.getByTestId("visual-style-select")
await user.click(visualStyleSelect)
// Should show style options (find all and check they exist)
const documentaryOptions = screen.getAllByText("Документальный")
expect(documentaryOptions.length).toBeGreaterThan(0)
expect(screen.getByText("Минималистичный")).toBeInTheDocument()
})
it("should allow setting target audience", async () => {
const audienceInput = screen.getByDisplayValue("Общая аудитория")
await user.clear(audienceInput)
await user.type(audienceInput, "Молодежь 18-25")
expect(screen.getByDisplayValue("Молодежь 18-25")).toBeInTheDocument()
})
it("should allow adjusting emotional intensity", async () => {
const slider = screen.getByRole("slider")
expect(slider).toBeInTheDocument()
// Slider should be interactive - just check it exists for now
// Slider interaction testing is complex with Radix UI
})
})
describe("Narrative Structure Step", () => {
beforeEach(async () => {
render()
// Navigate to narrative step
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
let nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton) // Go to style step
nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton) // Go to narrative step
})
it("should show narrative structure options", () => {
expect(screen.getByText("Шаг 3 из 6")).toBeInTheDocument()
expect(screen.getAllByText("Структура повествования").length).toBeGreaterThan(0)
expect(screen.getByText("Тип структуры")).toBeInTheDocument()
expect(screen.getByText("Темп повествования")).toBeInTheDocument()
})
it("should allow selecting narrative structure", async () => {
// Check that structure cards are displayed
expect(screen.getByText("Трёхактная структура")).toBeInTheDocument()
expect(screen.getByText("Пятиактная структура")).toBeInTheDocument()
expect(screen.getByText("Путешествие героя")).toBeInTheDocument()
// Test narrative style select
const structureSelect = screen.getByTestId("narrative-style-select")
await user.click(structureSelect)
expect(screen.getAllByText("Линейный").length).toBeGreaterThan(0)
expect(screen.getByText("Нелинейный")).toBeInTheDocument()
expect(screen.getByText("Монтажный")).toBeInTheDocument()
})
it("should show narrative structure selection cards", async () => {
// Check that narrative structure cards are present and clickable
const threeActCard = screen.getByText("Трёхактная структура").closest("div")
expect(threeActCard).toBeInTheDocument()
// Test clicking on a structure card
await user.click(threeActCard!)
// Check if the card gets selected by looking for data-selected attribute or specific classes that indicate selection
expect(threeActCard).toBeInTheDocument()
})
})
describe("Characters and Dialogue Step", () => {
beforeEach(async () => {
render()
// Navigate to characters step
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
for (let i = 0; i < 3; i++) {
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
}
})
it("should show character configuration options", () => {
expect(screen.getByText("Шаг 4 из 6")).toBeInTheDocument()
expect(screen.getAllByText("Персонажи и диалоги").length).toBeGreaterThan(0)
expect(screen.getByText("Количество персонажей")).toBeInTheDocument()
expect(screen.getByText("Включить диалоги")).toBeInTheDocument()
expect(screen.getByText("Включить рассказчика")).toBeInTheDocument()
})
it("should allow adjusting character count", async () => {
const characterSlider = screen.getByRole("slider")
expect(characterSlider).toBeInTheDocument()
// Check that current value is displayed
expect(screen.getByText("2")).toBeInTheDocument() // Default value
})
it("should toggle dialogue inclusion", async () => {
const dialogueSwitches = screen.getAllByRole("switch")
expect(dialogueSwitches.length).toBeGreaterThan(0)
// Click the first switch (dialogue inclusion)
await user.click(dialogueSwitches[0])
// Switch state should change
expect(dialogueSwitches[0]).toBeInTheDocument()
})
})
describe("Audio Settings Step", () => {
beforeEach(async () => {
render()
// Navigate to audio step
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
for (let i = 0; i < 4; i++) {
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
}
})
it("should show audio configuration options", () => {
expect(screen.getByText("Шаг 5 из 6")).toBeInTheDocument()
expect(screen.getAllByText("Аудио и озвучка").length).toBeGreaterThan(0)
expect(screen.getByText("Включить закадровый голос")).toBeInTheDocument()
expect(screen.getByText("Целевая длительность (секунды)")).toBeInTheDocument()
})
it("should allow selecting voiceover style", async () => {
const voiceoverSwitches = screen.getAllByRole("switch")
expect(voiceoverSwitches.length).toBeGreaterThan(0)
// Enable voiceover first
await user.click(voiceoverSwitches[0])
const styleSelect = screen.getByTestId("voiceover-style-select")
await user.click(styleSelect)
const narrativeOptions = screen.getAllByText("Повествовательный")
expect(narrativeOptions.length).toBeGreaterThan(0)
const documentaryOptions = screen.getAllByText("Документальный")
expect(documentaryOptions.length).toBeGreaterThan(0)
})
})
describe("Review Step", () => {
beforeEach(async () => {
render()
// Navigate to review step
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
for (let i = 0; i < 5; i++) {
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
}
})
it("should show review step with all settings", () => {
expect(screen.getByText("Шаг 6 из 6")).toBeInTheDocument()
expect(screen.getAllByText("Проверка параметров").length).toBeGreaterThan(0)
expect(screen.getByText("Проверьте все настройки перед генерацией скрипта.")).toBeInTheDocument()
})
it("should show generate button instead of next", () => {
expect(screen.getByRole("button", { name: /создать скрипт/i })).toBeInTheDocument()
expect(screen.queryByRole("button", { name: /далее/i })).not.toBeInTheDocument()
})
it("should show selected template information", () => {
expect(screen.getByText("Шаблон")).toBeInTheDocument()
// Template name appears multiple times, use getAllByText
expect(screen.getAllByText("Кинематографический рассказ").length).toBeGreaterThan(0)
})
})
describe("Script Generation", () => {
beforeEach(async () => {
render()
// Navigate to review step
const cinematicCard = screen.getByText("Кинематографический рассказ").closest("div")
await user.click(cinematicCard!)
for (let i = 0; i < 5; i++) {
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
}
})
it("should call generateScript when generate button is clicked", async () => {
const mockGeneratedScript = createMockGeneratedScript()
mockGenerateScript.mockResolvedValueOnce(mockGeneratedScript)
const generateButton = screen.getByRole("button", { name: /создать скрипт/i })
await user.click(generateButton)
await waitFor(() => {
expect(mockGenerateScript).toHaveBeenCalledWith(
mockAnalysis,
expect.objectContaining({
style: expect.objectContaining({
visual: "cinematic",
narrative: "linear",
editing: "continuity",
}),
targetAudience: "Общая аудитория",
includeDialogue: true,
includeVoiceover: false,
narrativeStructure: "three_act",
}),
)
})
})
it("should show generating state during script generation", async () => {
vi.mocked(useAIIntelligence).mockReturnValue({
...defaultAIIntelligenceMock,
isProcessing: true,
})
render()
// Navigate to review step quickly
const cinematicCards = screen.getAllByText("Кинематографический рассказ")
const cinematicCard = cinematicCards[0].closest("div")
await user.click(cinematicCard!)
for (let i = 0; i < 5; i++) {
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
}
const generateButton = screen.getByRole("button", { name: /создать скрипт/i })
await user.click(generateButton)
expect(screen.getAllByText("Генерация скрипта").length).toBeGreaterThan(0)
expect(screen.getByText("ИИ создаёт ваш скрипт на основе анализа и параметров...")).toBeInTheDocument()
})
it("should call onGenerate callback when script is generated", async () => {
const mockGeneratedScript = createMockGeneratedScript()
mockGenerateScript.mockResolvedValueOnce(mockGeneratedScript)
const generateButton = screen.getByRole("button", { name: /создать скрипт/i })
await user.click(generateButton)
await waitFor(() => {
expect(mockOnGenerate).toHaveBeenCalledWith(mockGeneratedScript)
})
})
it("should handle generation errors", async () => {
const mockError = new Error("Generation failed")
mockGenerateScript.mockRejectedValueOnce(mockError)
const generateButton = screen.getByRole("button", { name: /создать скрипт/i })
await user.click(generateButton)
await waitFor(() => {
expect(screen.getByText("Ошибка генерации")).toBeInTheDocument()
expect(screen.getByText("Generation failed")).toBeInTheDocument()
})
})
})
describe("Callbacks", () => {
it("should call onCancel when cancel button is clicked", async () => {
render()
const cancelButton = screen.getByRole("button", { name: /отмена/i })
await user.click(cancelButton)
expect(mockOnCancel).toHaveBeenCalled()
})
it("should call onClose when close action is triggered", async () => {
render()
// In the actual implementation, close might be triggered by completing generation
// For now, we'll test that the prop is handled
expect(mockOnClose).not.toHaveBeenCalled()
})
})
describe("Validation", () => {
it("should prevent navigation without template selection", () => {
render()
const nextButton = screen.getByRole("button", { name: /далее/i })
expect(nextButton).toBeDisabled()
})
it("should require analysis for script generation", () => {
render()
// Even if we navigate to the end, generation should require analysis
// This is a basic test - the actual validation logic would be more complex
expect(mockOnGenerate).not.toHaveBeenCalled()
})
})
describe("Template Presets", () => {
it("should apply documentary template settings", async () => {
render()
const documentaryCard = screen.getByText("Документальный").closest("div")
await user.click(documentaryCard!)
// Navigate to style step to see applied settings
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
// Documentary template should set appropriate defaults
expect(screen.getByText("Стиль и жанр")).toBeInTheDocument()
})
it("should apply social media template settings", async () => {
render()
const socialCard = screen.getByText("Социальные сети").closest("div")
await user.click(socialCard!)
const nextButton = screen.getByRole("button", { name: /далее/i })
await user.click(nextButton)
expect(screen.getByText("Стиль и жанр")).toBeInTheDocument()
})
})
})