import { render, screen, waitFor } from "@testing-library/react" import userEvent from "@testing-library/user-event" import { beforeEach, describe, expect, it, vi } from "vitest" import { VideoEffect } from "@/features/effects/types" import { BrowserProviders } from "@/test/test-utils" import { EffectParameterControls } from "../../components/effect-parameter-controls" // Mock useUserSettings vi.mock("@/features/user-settings/hooks/use-user-settings", () => ({ useUserSettings: () => ({ settings: {}, updateSettings: vi.fn(), }), })) // Mock window.prompt global.prompt = vi.fn() describe("EffectParameterControls", () => { const mockEffect: VideoEffect = { id: "test-effect", name: { en: "Test Effect", ru: "Тестовый эффект", }, category: "blur_sharpen", scope: ["clip"], processingType: "realtime", version: "1.0.0", tags: ["popular"], description: { ru: "Тестовый эффект", en: "Test effect" }, complexity: "low", gpuAccelerated: true, parameters: [ { id: "intensity", name: { en: "Intensity", ru: "Интенсивность" }, type: "number", defaultValue: 50, min: 0, max: 100, step: 1, animatable: true, }, { id: "radius", name: { en: "Radius", ru: "Радиус" }, type: "number", defaultValue: 5, min: 0, max: 50, step: 1, animatable: true, }, { id: "temperature", name: { en: "Temperature", ru: "Температура" }, type: "number", defaultValue: 0, min: -100, max: 100, step: 1, animatable: true, }, { id: "speed", name: { en: "Speed", ru: "Скорость" }, type: "number", defaultValue: 1.0, min: 0.1, max: 10, step: 0.1, animatable: true, }, ], presets: [], processors: { ffmpeg: { filter: (params) => `blur=${params.intensity || 50}`, }, }, } const mockOnParametersChange = vi.fn() const mockOnSavePreset = vi.fn() beforeEach(() => { vi.clearAllMocks() global.prompt = vi.fn() }) it("renders parameter controls for effect with params", () => { render( , ) expect(screen.getByText("effects.detail.parameters")).toBeInTheDocument() expect(screen.getByText("Интенсивность")).toBeInTheDocument() expect(screen.getByText("Радиус")).toBeInTheDocument() expect(screen.getByText("Температура")).toBeInTheDocument() expect(screen.getByText("Скорость")).toBeInTheDocument() }) it("renders null for effect without params", () => { const effectWithoutParams = { ...mockEffect, parameters: undefined } render( , ) // Should not render parameter controls expect(screen.queryByText("effects.detail.parameters")).not.toBeInTheDocument() expect(screen.queryByText("Интенсивность")).not.toBeInTheDocument() }) it("renders null for effect with empty params", () => { const effectWithEmptyParams = { ...mockEffect, parameters: [] } render( , ) // Should not render parameter controls expect(screen.queryByText("effects.detail.parameters")).not.toBeInTheDocument() expect(screen.queryByText("Интенсивность")).not.toBeInTheDocument() }) it("displays current parameter values", () => { render( , ) expect(screen.getByText("50")).toBeInTheDocument() // intensity expect(screen.getByText("5")).toBeInTheDocument() // radius expect(screen.getByText("0")).toBeInTheDocument() // temperature expect(screen.getByText("1")).toBeInTheDocument() // speed }) it("calls onParametersChange when slider value changes", async () => { const user = userEvent.setup() render( , ) const slider = screen.getAllByRole("slider")[0] // intensity slider // Use keyboard interaction instead of mouse for more reliable testing slider.focus() await user.keyboard("{ArrowRight}") await waitFor(() => { expect(mockOnParametersChange).toHaveBeenCalled() }) }) it("resets parameters to default values when reset button is clicked", async () => { const user = userEvent.setup() render( , ) const resetButton = screen.getByRole("button", { name: "RotateCcw" }) await user.click(resetButton) expect(mockOnParametersChange).toHaveBeenCalledWith({ intensity: 50, // default from PARAMETER_CONFIG radius: 5, temperature: 0, speed: 1.0, }) }) it("shows save preset button when onSavePreset is provided", () => { render( , ) expect(screen.getByRole("button", { name: "Save" })).toBeInTheDocument() }) it("does not show save preset button when onSavePreset is not provided", () => { render( , ) expect(screen.queryByRole("button", { name: "Save" })).not.toBeInTheDocument() }) it("saves preset with user-provided name", async () => { const user = userEvent.setup() global.prompt = vi.fn().mockReturnValue("My Custom Preset") render( , ) const saveButton = screen.getByRole("button", { name: "Save" }) await user.click(saveButton) expect(global.prompt).toHaveBeenCalledWith("effects.enterPresetName") expect(mockOnSavePreset).toHaveBeenCalledWith("My Custom Preset", { intensity: 50, radius: 5, temperature: 0, speed: 1.0, }) }) it("does not save preset when user cancels prompt", async () => { const user = userEvent.setup() global.prompt = vi.fn().mockReturnValue(null) render( , ) const saveButton = screen.getByRole("button", { name: "Save" }) await user.click(saveButton) expect(global.prompt).toHaveBeenCalled() expect(mockOnSavePreset).not.toHaveBeenCalled() }) it("does not save preset when user provides empty name", async () => { const user = userEvent.setup() global.prompt = vi.fn().mockReturnValue(" ") render( , ) const saveButton = screen.getByRole("button", { name: "Save" }) await user.click(saveButton) expect(global.prompt).toHaveBeenCalled() expect(mockOnSavePreset).not.toHaveBeenCalled() }) it("updates parameters when selectedPreset changes", async () => { const effectWithPresets = { ...mockEffect, presets: [ { id: "light", name: { ru: "Легкий", en: "Light" }, parameters: { intensity: 25, radius: 2, temperature: 10, speed: 0.5 }, description: { ru: "Легкий эффект", en: "Light effect" }, tags: [], }, ], } const { rerender } = render( , ) // Re-render with selectedPreset rerender( , ) await waitFor(() => { expect(mockOnParametersChange).toHaveBeenCalledWith({ intensity: 25, radius: 2, temperature: 10, speed: 0.5, }) }) }) it("displays current parameter values in info section", () => { render( , ) // Check that current values are displayed (i18n key may not be translated in test) expect(screen.getByText("intensity: 50")).toBeInTheDocument() expect(screen.getByText("radius: 5")).toBeInTheDocument() expect(screen.getByText("temperature: 0")).toBeInTheDocument() expect(screen.getByText("speed: 1")).toBeInTheDocument() }) it("shows tooltips with parameter descriptions", async () => { render( , ) // Check that sliders are present (they have tooltip containers) const sliders = screen.getAllByRole("slider") expect(sliders.length).toBe(4) // We have 4 parameters in mockEffect }) it("handles parameters not in PARAMETER_CONFIG", () => { const effectWithCustomParam = { ...mockEffect, parameters: [ ...mockEffect.parameters, { id: "customParam", name: { en: "Custom Param", ru: "Кастомный параметр" }, type: "number", defaultValue: 100, min: 0, max: 200, step: 1, animatable: false, }, ], } render( , ) expect(screen.getByText("Интенсивность")).toBeInTheDocument() // customParam might be rendered if PARAMETER_CONFIG includes it or uses a fallback // The test should check what the actual behavior is }) it("uses correct language for labels and descriptions", () => { render( , ) // Should use Russian labels as default in test environment expect(screen.getByText("Интенсивность")).toBeInTheDocument() expect(screen.getByText("Радиус")).toBeInTheDocument() expect(screen.getByText("Температура")).toBeInTheDocument() expect(screen.getByText("Скорость")).toBeInTheDocument() }) it("validates effect has parameters", () => { expect(mockEffect.parameters).toBeDefined() expect(mockEffect.parameters.length).toBeGreaterThan(0) }) it("should handle effect with presets", () => { const effectWithPresets = { ...mockEffect, presets: [ { id: "light", name: { ru: "Легкий", en: "Light" }, parameters: { intensity: 25, radius: 2 }, description: { ru: "Легкий эффект", en: "Light effect" }, tags: [], }, ], } expect(effectWithPresets.presets).toBeDefined() expect(effectWithPresets.presets[0]).toBeDefined() expect(effectWithPresets.presets[0].id).toBe("light") }) it("should validate parameter types", () => { const intensityParam = mockEffect.parameters.find((p) => p.id === "intensity") const radiusParam = mockEffect.parameters.find((p) => p.id === "radius") expect(intensityParam?.type).toBe("number") expect(radiusParam?.type).toBe("number") expect(intensityParam?.defaultValue).toBe(50) expect(radiusParam?.defaultValue).toBe(5) }) })