import { render, screen, waitFor } from "@testing-library/react"
import { useEffect, useState } from "react"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { resetTransitionsState } from "@/features/transitions/hooks/use-transitions"
import {
useEffects,
useEffectsSearch,
useFilters,
useFiltersSearch,
useLoadingState,
useResourceById,
useResourceSources,
useResources,
useResourcesAdapter,
useResourcesByCategory,
useResourcesByComplexity,
useResourcesByTags,
useResourcesCache,
useResourcesSearch,
useResourcesStats,
useTransitions,
useTransitionsSearch,
} from "../../hooks/use-resources"
import { EffectsProvider, resetEffectsProviderState } from "../../providers/effects-provider"
// Мокаем JSON файл с переходами
vi.mock("@/features/transitions/data/transitions.json", () => ({
default: {
transitions: [
{
id: "test-transition-1",
type: "fade",
labels: { ru: "Исчезновение", en: "Fade" },
description: { ru: "Плавное исчезновение", en: "Smooth fade" },
category: "basic",
complexity: "basic",
tags: ["smooth", "classic"],
duration: { min: 0.1, max: 5, default: 1 },
},
],
},
}))
// Мокаем ленивые загрузчики ресурсов
vi.mock("../../services/resource-loaders", () => ({
loadAllResourcesLazy: vi.fn().mockResolvedValue({
effects: {
success: true,
data: [
{
id: "test-effect-1",
name: "Test Effect 1",
type: "blur",
category: "artistic",
complexity: "basic",
tags: ["test", "popular"],
description: { ru: "Тестовый эффект 1", en: "Test Effect 1" },
ffmpegCommand: () => "blur=5",
params: { intensity: 50 },
previewPath: "/test1.mp4",
labels: { en: "Test Effect 1", ru: "Тестовый эффект 1" },
},
{
id: "test-effect-2",
name: "Test Effect 2",
type: "brightness",
category: "color-correction",
complexity: "intermediate",
tags: ["test", "color"],
description: { ru: "Тестовый эффект 2", en: "Test Effect 2" },
ffmpegCommand: () => "brightness=0.1",
params: { intensity: 75 },
previewPath: "/test2.mp4",
labels: { en: "Test Effect 2", ru: "Тестовый эффект 2" },
},
],
source: "built-in",
timestamp: Date.now(),
},
filters: {
success: true,
data: [
{
id: "test-filter-1",
name: "Test Filter 1",
category: "technical",
complexity: "advanced",
tags: ["log", "professional"],
description: { ru: "Тестовый фильтр", en: "Test Filter" },
labels: { en: "Test Filter", ru: "Тестовый фильтр" },
},
],
source: "built-in",
timestamp: Date.now(),
},
transitions: {
success: true,
data: [
{
id: "test-transition-1",
type: "fade",
labels: { ru: "Исчезновение", en: "Fade" },
description: { ru: "Плавное исчезновение", en: "Smooth fade" },
category: "basic",
complexity: "basic",
tags: ["smooth", "classic"],
duration: { min: 0.1, max: 5, default: 1 },
},
],
source: "built-in",
timestamp: Date.now(),
},
}),
}))
// Общая очистка состояния перед каждым тестом
beforeEach(async () => {
vi.clearAllMocks()
resetEffectsProviderState()
resetTransitionsState()
// Небольшая задержка для гарантии очистки состояния
await new Promise((resolve) => setTimeout(resolve, 10))
})
// Вспомогательная функция для ожидания загрузки провайдера
const waitForProviderReady = async () => {
await waitFor(
() => {
expect(screen.getByTestId("loading")).toHaveTextContent("false")
},
{ timeout: 5000 },
)
// Дополнительная задержка для стабильности
await new Promise((resolve) => setTimeout(resolve, 50))
}
describe("useEffects", () => {
function TestComponent() {
const { effects, loading } = useEffects()
return (
{String(loading)}
{effects.length}
{effects.map((effect) => (
{effect.name}
))}
)
}
it("должен загружать и возвращать эффекты", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("effects-count")).toHaveTextContent("2")
expect(screen.getByText("Test Effect 1")).toBeInTheDocument()
expect(screen.getByText("Test Effect 2")).toBeInTheDocument()
})
})
describe("useFilters", () => {
function TestComponent() {
const { filters, loading } = useFilters()
return (
{String(loading)}
{filters.length}
{filters.map((filter) => (
{filter.name}
))}
)
}
it("должен загружать и возвращать фильтры", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("filters-count")).toHaveTextContent("1")
expect(screen.getByText("Test Filter 1")).toBeInTheDocument()
})
})
describe("useTransitions", () => {
function TestComponent() {
const { transitions, loading } = useTransitions()
return (
{String(loading)}
{transitions.length}
{transitions.map((transition) => (
{transition.labels?.en}
))}
)
}
it("должен загружать и возвращать переходы", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("transitions-count")).toHaveTextContent("1")
expect(screen.getByText("Fade")).toBeInTheDocument()
})
})
describe("useResourceById", () => {
function TestComponent({ id }: { id: string }) {
const { resource, loading } = useResourceById("effect", id)
return (
{String(loading)}
{resource?.name || "Not found"}
)
}
it("должен находить ресурс по ID", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("resource-name")).toHaveTextContent("Test Effect 1")
})
it("должен возвращать null для несуществующего ID", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("resource-name")).toHaveTextContent("Not found")
})
})
describe("useResourcesSearch", () => {
function TestComponent({ options }: { options: any }) {
const { results, loading } = useResourcesSearch("effect", options)
return (
{String(loading)}
{results.length}
{results.map((item) => (
{item.name}
))}
)
}
it("должен выполнять поиск по запросу", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("results-count")).toHaveTextContent("1")
expect(screen.getByText("Test Effect 1")).toBeInTheDocument()
})
it("должен фильтровать по категории", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("results-count")).toHaveTextContent("1")
expect(screen.getByText("Test Effect 1")).toBeInTheDocument()
})
it("должен фильтровать по тегам", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("results-count")).toHaveTextContent("1")
expect(screen.getByText("Test Effect 1")).toBeInTheDocument()
})
it("должен фильтровать по сложности", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("results-count")).toHaveTextContent("1")
expect(screen.getByText("Test Effect 2")).toBeInTheDocument()
})
})
describe("useResourcesByCategory", () => {
function TestComponent({ category }: { category: string }) {
const { results } = useResourcesByCategory("effect", category)
return (
)
}
it("должен возвращать ресурсы по категории", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByTestId("results-count")).toHaveTextContent("1")
})
})
})
describe("useResourcesByTags", () => {
function TestComponent({ tags }: { tags: string[] }) {
const { results } = useResourcesByTags("effect", tags)
return (
)
}
it("должен возвращать ресурсы по тегам", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByTestId("results-count")).toHaveTextContent("1")
})
})
})
describe("useResourcesByComplexity", () => {
function TestComponent({ complexity }: { complexity: string }) {
const { results } = useResourcesByComplexity("effect", complexity)
return (
)
}
it("должен возвращать ресурсы по сложности", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByTestId("results-count")).toHaveTextContent("1")
})
})
})
describe("useLoadingState", () => {
function TestComponent() {
const loadingState = useLoadingState()
return (
{String(loadingState.isLoading)}
{loadingState.progress}
{loadingState.loadedSources.size}
)
}
it("должен возвращать состояние загрузки", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByTestId("is-loading")).toHaveTextContent("false")
})
expect(screen.getByTestId("loaded-sources")).toHaveTextContent("1")
})
})
describe("useResourcesStats", () => {
function TestComponent() {
const stats = useResourcesStats()
return (
{stats.total}
{stats.byType.effect}
{stats.byType.filter}
{stats.byType.transition}
)
}
it("должен возвращать статистику ресурсов", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByTestId("total")).toHaveTextContent("4")
})
expect(screen.getByTestId("effects")).toHaveTextContent("2")
expect(screen.getByTestId("filters")).toHaveTextContent("1")
expect(screen.getByTestId("transitions")).toHaveTextContent("1")
})
})
describe("useResources", () => {
function TestComponent({ type }: { type: any }) {
const { resources, loading } = useResources(type)
return (
{String(loading)}
{resources.length}
{resources.map((resource) => (
{resource.name}
))}
)
}
it("должен загружать ресурсы по типу", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("resources-count")).toHaveTextContent("2")
expect(screen.getByText("Test Effect 1")).toBeInTheDocument()
expect(screen.getByText("Test Effect 2")).toBeInTheDocument()
})
})
describe("useResourceSources", () => {
function TestComponent() {
const { loadSource, refreshSource, isSourceLoaded, getSourceConfig, updateSourceConfig, loadingState } =
useResourceSources()
const [loaded, setLoaded] = useState(false)
const [config, setConfig] = useState(null)
const [refreshed, setRefreshed] = useState(false)
useEffect(() => {
setLoaded(isSourceLoaded("built-in"))
setConfig(getSourceConfig("built-in"))
}, [isSourceLoaded, getSourceConfig])
const handleLoad = async () => {
// Загружаем встроенный источник заново для теста
await loadSource("built-in")
}
const handleRefresh = async () => {
await refreshSource("built-in")
setRefreshed(true)
}
const handleUpdateConfig = () => {
updateSourceConfig("built-in", { test: true })
}
return (
{String(loaded)}
{JSON.stringify(config)}
{String(loadingState.isLoading)}
{String(refreshed)}
)
}
it("должен управлять источниками данных", async () => {
render(
,
)
// Ждем пока loading state станет false
await waitFor(
() => {
expect(screen.getByTestId("loading-state")).toHaveTextContent("false")
},
{ timeout: 5000 },
)
// Проверяем что config доступен
expect(screen.getByTestId("config")).toHaveTextContent("built-in")
// Проверяем что кнопки управления источниками доступны
expect(screen.getByText("Load Built-in")).toBeInTheDocument()
expect(screen.getByText("Refresh")).toBeInTheDocument()
expect(screen.getByText("Update Config")).toBeInTheDocument()
})
})
describe("useResourcesCache", () => {
function TestComponent() {
const { clearCache, clearSourceCache, invalidateCache, getCacheSize } = useResourcesCache()
const [cacheSize, setCacheSize] = useState(0)
useEffect(() => {
setCacheSize(getCacheSize())
}, [getCacheSize])
const handleClearCache = () => {
clearCache("effect")
setCacheSize(getCacheSize())
}
const handleClearSourceCache = () => {
clearSourceCache("built-in")
setCacheSize(getCacheSize())
}
const handleInvalidateCache = () => {
invalidateCache()
setCacheSize(getCacheSize())
}
return (
{cacheSize}
)
}
it("должен управлять кэшем", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByTestId("cache-size")).toBeInTheDocument()
})
// Проверяем, что методы кэша доступны
const clearButton = screen.getByText("Clear Effects Cache")
const clearSourceButton = screen.getByText("Clear Source Cache")
const invalidateButton = screen.getByText("Invalidate Cache")
expect(clearButton).toBeInTheDocument()
expect(clearSourceButton).toBeInTheDocument()
expect(invalidateButton).toBeInTheDocument()
})
})
describe("useResourcesAdapter", () => {
function TestComponent({ type, options }: { type: any; options?: any }) {
const adapter = useResourcesAdapter({ type, searchOptions: options })
return (
{adapter.items.length}
{String(adapter.loading)}
{adapter.error || "none"}
{adapter.stats.total}
{adapter.items.map((item) => (
{item.name}
))}
)
}
it("должен предоставлять унифицированный интерфейс адаптера", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("items-count")).toHaveTextContent("2")
expect(screen.getByTestId("loading")).toHaveTextContent("false")
expect(screen.getByTestId("error")).toHaveTextContent("none")
expect(screen.getByTestId("total-stats")).toHaveTextContent("4")
})
it("должен фильтровать через адаптер", async () => {
render(
,
)
await waitForProviderReady()
expect(screen.getByTestId("items-count")).toHaveTextContent("1")
expect(screen.getByText("Test Effect 1")).toBeInTheDocument()
})
})
describe("Typed search hooks", () => {
function TestEffectsSearch() {
const { results } = useEffectsSearch({ category: "artistic" })
return (
)
}
function TestFiltersSearch() {
const { results } = useFiltersSearch({ category: "technical" })
return (
)
}
function TestTransitionsSearch() {
const { results } = useTransitionsSearch({ category: "basic" })
return (
)
}
it("должен выполнять типизированный поиск эффектов", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByTestId("effects-search-count")).toHaveTextContent("1")
})
})
it("должен выполнять типизированный поиск фильтров", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByTestId("filters-search-count")).toHaveTextContent("1")
})
})
it("должен выполнять типизированный поиск переходов", async () => {
render(
,
)
await waitFor(() => {
expect(screen.getByTestId("transitions-search-count")).toHaveTextContent("1")
})
})
})