/** * 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() }) }) })