import { fireEvent, render, screen } from "@testing-library/react" import React from "react" import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" import { AudioEffectsEditorModal } from "@/features/timeline/components/audio-effects-editor-modal" // Mock the modal service vi.mock("@/features/modals/services", () => ({ useModal: vi.fn(), })) // Mock UI components vi.mock("@/components/ui/button", () => ({ Button: ({ children, onClick, variant, ...props }: any) => ( ), })) vi.mock("@/components/ui/label", () => ({ Label: ({ children, ...props }: any) => , })) vi.mock("@/components/ui/slider", () => ({ Slider: ({ value, onValueChange, min, max, step, ...props }: any) => ( onValueChange?.([Number.parseFloat(e.target.value)])} min={min} max={max} step={step} data-testid="slider" {...props} /> ), })) vi.mock("@/components/ui/switch", () => ({ Switch: ({ checked, onCheckedChange, ...props }: any) => ( ), })) // Track active tab globally for tests let activeTab = "basic" vi.mock("@/components/ui/tabs", () => ({ Tabs: ({ children, value, onValueChange, ...props }: any) => { // Update active tab when value changes if (value) activeTab = value return (
{React.Children.map(children, (child) => React.isValidElement(child) ? React.cloneElement(child as any, { onValueChange }) : child, )}
) }, TabsList: ({ children, onValueChange, ...props }: any) => (
{React.Children.map(children, (child) => React.isValidElement(child) ? React.cloneElement(child as any, { onValueChange }) : child, )}
), TabsTrigger: ({ children, value, onValueChange, ...props }: any) => ( ), TabsContent: ({ children, value, ...props }: any) => { return activeTab === value ?
{children}
: null }, })) describe("AudioEffectsEditorModal", () => { const mockCloseModal = vi.fn() const mockOnApplyEffects = vi.fn() const mockUseModal = vi.fn() beforeEach(async () => { vi.clearAllMocks() activeTab = "basic" // Reset tab state const { useModal } = await import("@/features/modals/services") vi.mocked(useModal).mockImplementation(mockUseModal) mockUseModal.mockReturnValue({ modalData: { clip: { id: "test-clip" }, track: { id: "test-track" }, onApplyEffects: mockOnApplyEffects, activeEffects: {}, }, closeModal: mockCloseModal, }) }) afterEach(() => { vi.resetAllMocks() }) describe("initial rendering", () => { it("should render with default tab selected", () => { render() expect(screen.getByText("Базовые")).toBeInTheDocument() expect(screen.getByText("Динамика")).toBeInTheDocument() expect(screen.getByText("Пространство")).toBeInTheDocument() expect(screen.getByText("Коррекция")).toBeInTheDocument() }) it("should render basic effects in default tab", () => { render() expect(screen.getByText("Fade In")).toBeInTheDocument() expect(screen.getByText("Fade Out")).toBeInTheDocument() expect(screen.getByText("Эквалайзер")).toBeInTheDocument() }) it("should render action buttons", () => { render() expect(screen.getByText("Отмена")).toBeInTheDocument() expect(screen.getByText("Применить эффекты")).toBeInTheDocument() }) it("should load active effects from modal data", () => { mockUseModal.mockReturnValue({ modalData: { activeEffects: { "fade-in": { id: "fade-in", name: "Fade In", type: "AudioFadeIn", enabled: true, params: { duration: 2.0 }, }, }, onApplyEffects: mockOnApplyEffects, }, closeModal: mockCloseModal, }) render() const fadeInSwitch = screen.getAllByRole("switch")[0] expect(fadeInSwitch).toHaveAttribute("aria-checked", "true") }) }) describe("effect toggling", () => { it("should toggle fade in effect", () => { render() const fadeInSwitch = screen.getAllByRole("switch")[0] expect(fadeInSwitch).toHaveAttribute("aria-checked", "false") fireEvent.click(fadeInSwitch) expect(fadeInSwitch).toHaveAttribute("aria-checked", "true") // Should show fade in controls expect(screen.getByText("Длительность (сек)")).toBeInTheDocument() }) it("should toggle fade out effect", () => { render() const fadeOutSwitch = screen.getAllByRole("switch")[1] fireEvent.click(fadeOutSwitch) expect(fadeOutSwitch).toHaveAttribute("aria-checked", "true") expect(screen.getAllByText("Длительность (сек)")).toHaveLength(1) }) it("should toggle equalizer effect", () => { render() const equalizerSwitch = screen.getAllByRole("switch")[2] fireEvent.click(equalizerSwitch) expect(equalizerSwitch).toHaveAttribute("aria-checked", "true") expect(screen.getByText("Низкие частоты (100Hz)")).toBeInTheDocument() expect(screen.getByText("Средние частоты (1kHz)")).toBeInTheDocument() expect(screen.getByText("Высокие частоты (10kHz)")).toBeInTheDocument() }) it("should remove effect when toggled off", () => { render() const fadeInSwitch = screen.getAllByRole("switch")[0] // Toggle on fireEvent.click(fadeInSwitch) expect(screen.getByText("Длительность (сек)")).toBeInTheDocument() // Toggle off fireEvent.click(fadeInSwitch) expect(screen.queryByText("Длительность (сек)")).not.toBeInTheDocument() }) }) describe("parameter adjustment", () => { it("should update fade in duration", () => { render() const fadeInSwitch = screen.getAllByRole("switch")[0] fireEvent.click(fadeInSwitch) const slider = screen.getByTestId("slider") fireEvent.change(slider, { target: { value: "3.5" } }) expect(slider).toHaveValue("3.5") }) it("should update equalizer parameters", () => { render() const equalizerSwitch = screen.getAllByRole("switch")[2] fireEvent.click(equalizerSwitch) const sliders = screen.getAllByTestId("slider") // Low frequency fireEvent.change(sliders[0], { target: { value: "5" } }) expect(sliders[0]).toHaveValue("5") // Mid frequency fireEvent.change(sliders[1], { target: { value: "-3" } }) expect(sliders[1]).toHaveValue("-3") // High frequency fireEvent.change(sliders[2], { target: { value: "2" } }) expect(sliders[2]).toHaveValue("2") }) }) describe("tab navigation", () => { it("should switch to dynamics tab", () => { render() const dynamicsTab = screen.getByText("Динамика") fireEvent.click(dynamicsTab) expect(screen.getByText("Компрессор")).toBeInTheDocument() expect(screen.getByText("Нормализация")).toBeInTheDocument() }) it("should show compressor controls when enabled", () => { render() // Switch to dynamics tab const dynamicsTab = screen.getByText("Динамика") fireEvent.click(dynamicsTab) const compressorSwitch = screen.getAllByRole("switch")[0] fireEvent.click(compressorSwitch) expect(screen.getByText("Порог (dB)")).toBeInTheDocument() expect(screen.getByText("Степень сжатия")).toBeInTheDocument() }) it("should update compressor attack and release parameters", () => { render() // Switch to dynamics tab const dynamicsTab = screen.getByText("Динамика") fireEvent.click(dynamicsTab) // Enable compressor const compressorSwitch = screen.getAllByRole("switch")[0] fireEvent.click(compressorSwitch) const sliders = screen.getAllByTestId("slider") // Update threshold fireEvent.change(sliders[0], { target: { value: "-30" } }) expect(sliders[0]).toHaveValue("-30") // Update ratio fireEvent.change(sliders[1], { target: { value: "8" } }) expect(sliders[1]).toHaveValue("8") }) it("should show normalize controls and update parameters", () => { render() // Switch to dynamics tab const dynamicsTab = screen.getByText("Динамика") fireEvent.click(dynamicsTab) // Enable normalize const normalizeSwitch = screen.getAllByRole("switch")[1] fireEvent.click(normalizeSwitch) expect(screen.getByText("Целевая громкость (LUFS)")).toBeInTheDocument() const slider = screen.getByTestId("slider") fireEvent.change(slider, { target: { value: "-16" } }) expect(slider).toHaveValue("-16") }) }) describe("spatial effects", () => { it("should show reverb controls", () => { render() // Switch to spatial tab const spatialTab = screen.getByText("Пространство") fireEvent.click(spatialTab) expect(screen.getByText("Реверберация")).toBeInTheDocument() const reverbSwitch = screen.getAllByRole("switch")[0] fireEvent.click(reverbSwitch) expect(screen.getByText("Размер помещения")).toBeInTheDocument() expect(screen.getByText("Микс (Dry/Wet)")).toBeInTheDocument() }) it("should update reverb damping parameter", () => { render() // Switch to spatial tab const spatialTab = screen.getByText("Пространство") fireEvent.click(spatialTab) // Enable reverb const reverbSwitch = screen.getAllByRole("switch")[0] fireEvent.click(reverbSwitch) const sliders = screen.getAllByTestId("slider") // Update room size fireEvent.change(sliders[0], { target: { value: "0.8" } }) expect(sliders[0]).toHaveValue("0.8") // Update wet mix fireEvent.change(sliders[1], { target: { value: "0.5" } }) expect(sliders[1]).toHaveValue("0.5") }) it("should show delay controls", () => { render() // Switch to spatial tab const spatialTab = screen.getByText("Пространство") fireEvent.click(spatialTab) expect(screen.getByText("Задержка (Delay)")).toBeInTheDocument() const delaySwitch = screen.getAllByRole("switch")[1] fireEvent.click(delaySwitch) expect(screen.getByText("Время задержки (сек)")).toBeInTheDocument() expect(screen.getByText("Затухание")).toBeInTheDocument() }) }) describe("correction effects", () => { it("should show denoise controls", () => { render() // Switch to correction tab const correctionTab = screen.getByText("Коррекция") fireEvent.click(correctionTab) expect(screen.getByText("Шумоподавление")).toBeInTheDocument() const denoiseSwitch = screen.getByRole("switch") fireEvent.click(denoiseSwitch) expect(screen.getByText("Сила подавления")).toBeInTheDocument() }) }) describe("applying effects", () => { it("should apply selected effects", () => { render() // Enable fade in const fadeInSwitch = screen.getAllByRole("switch")[0] fireEvent.click(fadeInSwitch) // Enable equalizer const equalizerSwitch = screen.getAllByRole("switch")[2] fireEvent.click(equalizerSwitch) // Apply const applyButton = screen.getByText("Применить эффекты") fireEvent.click(applyButton) expect(mockOnApplyEffects).toHaveBeenCalledWith([ expect.objectContaining({ effectId: "fade-in", enabled: true, order: 0, customParams: { duration: 1.0 }, }), expect.objectContaining({ effectId: "equalizer", enabled: true, order: 1, customParams: { gain_low: 0, gain_mid: 0, gain_high: 0 }, }), ]) expect(mockCloseModal).toHaveBeenCalled() }) it("should apply effects with modified parameters", () => { render() // Enable fade in and modify duration const fadeInSwitch = screen.getAllByRole("switch")[0] fireEvent.click(fadeInSwitch) const slider = screen.getByTestId("slider") fireEvent.change(slider, { target: { value: "2.5" } }) // Apply const applyButton = screen.getByText("Применить эффекты") fireEvent.click(applyButton) expect(mockOnApplyEffects).toHaveBeenCalledWith([ expect.objectContaining({ effectId: "fade-in", customParams: { duration: 2.5 }, }), ]) }) it("should not call onApplyEffects if not provided", () => { mockUseModal.mockReturnValue({ modalData: { clip: { id: "test-clip" }, onApplyEffects: undefined, }, closeModal: mockCloseModal, }) render() const applyButton = screen.getByText("Применить эффекты") fireEvent.click(applyButton) expect(mockCloseModal).toHaveBeenCalled() }) }) describe("cancel functionality", () => { it("should close modal on cancel", () => { render() const cancelButton = screen.getByText("Отмена") fireEvent.click(cancelButton) expect(mockCloseModal).toHaveBeenCalled() expect(mockOnApplyEffects).not.toHaveBeenCalled() }) }) describe("edge cases", () => { it("should handle missing modal data", () => { mockUseModal.mockReturnValue({ modalData: null, closeModal: mockCloseModal, }) render() expect(screen.getByText("Fade In")).toBeInTheDocument() }) it("should handle empty active effects", () => { mockUseModal.mockReturnValue({ modalData: { activeEffects: null, onApplyEffects: mockOnApplyEffects, }, closeModal: mockCloseModal, }) render() const switches = screen.getAllByRole("switch") switches.forEach((switchEl) => { expect(switchEl).toHaveAttribute("aria-checked", "false") }) }) it("should maintain correct order when applying multiple effects", () => { render() // Enable fade in (order 0) const fadeInSwitch = screen.getAllByRole("switch")[0] fireEvent.click(fadeInSwitch) // Enable equalizer (order 1) const equalizerSwitch = screen.getAllByRole("switch")[2] fireEvent.click(equalizerSwitch) // Switch to dynamics tab and enable compressor (order 2) const dynamicsTab = screen.getByText("Динамика") fireEvent.click(dynamicsTab) const compressorSwitch = screen.getAllByRole("switch")[0] fireEvent.click(compressorSwitch) // Apply const applyButton = screen.getByText("Применить эффекты") fireEvent.click(applyButton) expect(mockOnApplyEffects).toHaveBeenCalledWith([ expect.objectContaining({ effectId: "fade-in", order: 0, }), expect.objectContaining({ effectId: "equalizer", order: 1, }), expect.objectContaining({ effectId: "compressor", order: 2, }), ]) }) it("should preserve effects state when switching tabs", () => { render() // Enable fade in in basic tab const fadeInSwitch = screen.getAllByRole("switch")[0] fireEvent.click(fadeInSwitch) // Change fade in duration const fadeInSlider = screen.getByTestId("slider") fireEvent.change(fadeInSlider, { target: { value: "3" } }) // Switch to dynamics tab const dynamicsTab = screen.getByText("Динамика") fireEvent.click(dynamicsTab) // Enable compressor const compressorSwitch = screen.getAllByRole("switch")[0] fireEvent.click(compressorSwitch) // Switch back to basic tab const basicTab = screen.getByText("Базовые") fireEvent.click(basicTab) // Verify fade in is still enabled with the correct duration const basicSwitches = screen.getAllByRole("switch") expect(basicSwitches[0]).toHaveAttribute("aria-checked", "true") const sliders = screen.getAllByTestId("slider") expect(sliders[0]).toHaveValue("3") // Apply and verify both effects are included const applyButton = screen.getByText("Применить эффекты") fireEvent.click(applyButton) expect(mockOnApplyEffects).toHaveBeenCalledWith([ expect.objectContaining({ effectId: "fade-in", customParams: { duration: 3 }, }), expect.objectContaining({ effectId: "compressor", }), ]) }) it("should apply effects from multiple tabs correctly", () => { render() // Enable effects from different tabs // Basic tab: Enable fade in and equalizer const fadeInSwitch = screen.getAllByRole("switch")[0] fireEvent.click(fadeInSwitch) const equalizerSwitch = screen.getAllByRole("switch")[2] fireEvent.click(equalizerSwitch) // Dynamics tab const dynamicsTab = screen.getByText("Динамика") fireEvent.click(dynamicsTab) const normalizeSwitch = screen.getAllByRole("switch")[1] fireEvent.click(normalizeSwitch) // Spatial tab const spatialTab = screen.getByText("Пространство") fireEvent.click(spatialTab) const reverbSwitch = screen.getAllByRole("switch")[0] fireEvent.click(reverbSwitch) // Correction tab const correctionTab = screen.getByText("Коррекция") fireEvent.click(correctionTab) const denoiseSwitch = screen.getByRole("switch") fireEvent.click(denoiseSwitch) // Apply all effects const applyButton = screen.getByText("Применить эффекты") fireEvent.click(applyButton) expect(mockOnApplyEffects).toHaveBeenCalledWith([ expect.objectContaining({ effectId: "fade-in" }), expect.objectContaining({ effectId: "equalizer" }), expect.objectContaining({ effectId: "normalize" }), expect.objectContaining({ effectId: "reverb" }), expect.objectContaining({ effectId: "denoise" }), ]) // Verify all effects have correct order const calledEffects = mockOnApplyEffects.mock.calls[0][0] expect(calledEffects[0].order).toBe(0) expect(calledEffects[1].order).toBe(1) expect(calledEffects[2].order).toBe(2) expect(calledEffects[3].order).toBe(3) expect(calledEffects[4].order).toBe(4) }) it("should apply all effect parameters correctly", () => { render() // Enable and configure fade in const fadeInSwitch = screen.getAllByRole("switch")[0] fireEvent.click(fadeInSwitch) const fadeInSlider = screen.getByTestId("slider") fireEvent.change(fadeInSlider, { target: { value: "2.5" } }) // Enable and configure equalizer const equalizerSwitch = screen.getAllByRole("switch")[2] fireEvent.click(equalizerSwitch) const eqSliders = screen.getAllByTestId("slider") fireEvent.change(eqSliders[1], { target: { value: "-5" } }) // Low fireEvent.change(eqSliders[2], { target: { value: "3" } }) // Mid fireEvent.change(eqSliders[3], { target: { value: "7" } }) // High // Switch to dynamics tab and configure compressor const dynamicsTab = screen.getByText("Динамика") fireEvent.click(dynamicsTab) const compressorSwitch = screen.getAllByRole("switch")[0] fireEvent.click(compressorSwitch) const compressorSliders = screen.getAllByTestId("slider") fireEvent.change(compressorSliders[0], { target: { value: "-25" } }) // Threshold fireEvent.change(compressorSliders[1], { target: { value: "6" } }) // Ratio // Switch to spatial tab and configure reverb const spatialTab = screen.getByText("Пространство") fireEvent.click(spatialTab) const reverbSwitch = screen.getAllByRole("switch")[0] fireEvent.click(reverbSwitch) const reverbSliders = screen.getAllByTestId("slider") fireEvent.change(reverbSliders[0], { target: { value: "0.7" } }) // Room size fireEvent.change(reverbSliders[1], { target: { value: "0.4" } }) // Wet // Apply const applyButton = screen.getByText("Применить эффекты") fireEvent.click(applyButton) expect(mockOnApplyEffects).toHaveBeenCalledWith([ expect.objectContaining({ effectId: "fade-in", customParams: { duration: 2.5 }, }), expect.objectContaining({ effectId: "equalizer", customParams: { gain_low: -5, gain_mid: 3, gain_high: 7 }, }), expect.objectContaining({ effectId: "compressor", customParams: expect.objectContaining({ threshold: -25, ratio: 6, attack: 5, // Default values preserved release: 50, }), }), expect.objectContaining({ effectId: "reverb", customParams: expect.objectContaining({ room_size: 0.7, wet: 0.4, damping: 0.5, // Default value preserved }), }), ]) }) }) })