import { renderHook } from "@testing-library/react" import { beforeEach, describe, expect, it, vi } from "vitest" import { useStyleTemplatesAdapter } from "../../adapters/use-style-templates-adapter" // Минимальные моки для тестирования const mockStyleTemplates = [ { id: "intro-fade", name: "Плавное появление", description: "Плавная анимация появления", category: "intro", style: "modern", aspectRatio: "16:9", duration: 2, hasAnimation: true, hasText: false, thumbnail: "/style-templates/fade-intro.png", tags: ["вступление", "плавное", "intro", "fade"], }, { id: "title-bounce", name: "Подпрыгивающий заголовок", description: "Анимация подпрыгивающего заголовка", category: "title", style: "creative", aspectRatio: "16:9", duration: 1.5, hasAnimation: true, hasText: true, thumbnail: "/style-templates/bounce-title.png", tags: ["заголовок", "анимация", "title", "bounce"], }, { id: "outro-minimal", name: "Минималистичное завершение", description: "Чистое минималистичное завершение", category: "outro", style: "minimal", aspectRatio: "9:16", duration: 15, hasAnimation: false, hasText: true, thumbnail: null, tags: ["завершение", "минимализм", "outro", "minimal"], }, ] // Создаем реальные функции для тестирования логики const mockGetSortValue = vi.fn((template: any, sortBy: string) => { switch (sortBy) { case "name": return template.name.toLowerCase() case "category": return template.category.toLowerCase() case "style": return template.style.toLowerCase() case "duration": return template.duration case "aspectRatio": return template.aspectRatio default: return template.name.toLowerCase() } }) const mockGetSearchableText = vi.fn((template: any) => { const texts = [ template.name, template.description || "", template.category, template.style, template.aspectRatio, ...(template.tags || []), ] return texts.filter(Boolean) }) const mockGetGroupValue = vi.fn((template: any, groupBy: string) => { switch (groupBy) { case "category": return template.category || "other" case "style": return template.style || "other" case "aspectRatio": return template.aspectRatio || "16:9" case "duration": if (template.duration <= 3) return "Короткие (≤3с)" if (template.duration <= 10) return "Средние (3-10с)" return "Длинные (>10с)" case "features": if (template.hasText && template.hasAnimation) return "Текст + анимация" if (template.hasText) return "С текстом" if (template.hasAnimation) return "С анимацией" return "Базовые" default: return "" } }) const mockMatchesFilter = vi.fn((template: any, filterType: string) => { if (filterType === "all") return true // Фильтрация по категории if (["intro", "outro", "lower-third", "title", "transition", "overlay"].includes(filterType)) { return template.category === filterType } // Фильтрация по стилю if (["modern", "vintage", "minimal", "corporate", "creative", "cinematic"].includes(filterType)) { return template.style === filterType } // Фильтрация по соотношению сторон if (["16:9", "9:16", "1:1"].includes(filterType)) { return template.aspectRatio === filterType } // Фильтрация по наличию текста if (filterType === "hasText") { return template.hasText } // Фильтрация по наличию анимации if (filterType === "hasAnimation") { return template.hasAnimation } return true }) vi.mock("@/features/browser/hooks/use-resources", () => ({ useStyleTemplatesAdapter: vi.fn(() => ({ items: mockStyleTemplates, loading: false, error: null, stats: { total: 3 }, getSortValue: mockGetSortValue, getSearchableText: mockGetSearchableText, getGroupValue: mockGetGroupValue, matchesFilter: mockMatchesFilter, PreviewComponent: ({ item }: any) =>
{item.name}
, favoriteType: "template", isFavorite: vi.fn(() => false), })), })) vi.mock("@/features/app-state", () => ({ AppSettingsProvider: ({ children }: any) => children, useFavorites: vi.fn(() => ({ isItemFavorite: vi.fn(() => false), })), })) vi.mock("@/features/drag-drop", () => ({ useDraggable: vi.fn(() => ({})), })) vi.mock("@/features/style-templates/components/style-template-preview", () => ({ StyleTemplatePreview: ({ template, onSelect }: any) => (
onSelect(template.id)}> {template.name}
), })) vi.mock("@/features/browser/providers/effects-provider", () => ({ EffectsProvider: ({ children }: any) => children, useEffectsProvider: vi.fn(() => ({ api: { getEffects: vi.fn(() => []), getFilters: vi.fn(() => []), getTransitions: vi.fn(() => []), getTemplates: vi.fn(() => []), getResources: vi.fn(() => []), getResourceById: vi.fn(() => null), searchResources: vi.fn(() => []), getResourcesByCategory: vi.fn(() => []), getResourcesByTags: vi.fn(() => []), getResourcesByComplexity: vi.fn(() => []), loadSource: vi.fn(() => Promise.resolve({ success: true, data: [], source: "built-in", timestamp: Date.now() })), isSourceLoaded: vi.fn(() => true), refreshSource: vi.fn(() => Promise.resolve({ success: true, data: [], source: "built-in", timestamp: Date.now() }), ), preloadCategory: vi.fn(() => Promise.resolve({ success: true, data: [], source: "built-in", timestamp: Date.now() }), ), getSourceConfig: vi.fn(() => null), updateSourceConfig: vi.fn(), getLoadingState: vi.fn(() => ({ isLoading: false, loadedSources: new Set(["built-in"]), loadingQueue: [], error: null, progress: 100, })), getStats: vi.fn(() => ({ total: 0, byType: {}, bySource: {}, cacheSize: 0, memoryUsage: 0, })), getCacheSize: vi.fn(() => 0), clearCache: vi.fn(), clearSourceCache: vi.fn(), invalidateCache: vi.fn(), onLoadingStateChange: vi.fn(() => () => {}), onResourcesUpdate: vi.fn(() => () => {}), onError: vi.fn(() => () => {}), }, config: { initialSources: ["built-in"], backgroundLoadDelay: 1000, enableCaching: true, maxCacheSize: 100, }, isInitialized: true, })), })) // Простой враппер для тестов function TestWrapper({ children }: { children: React.ReactNode }) { return
{children}
} describe("useStyleTemplatesAdapter", () => { beforeEach(() => { vi.clearAllMocks() }) it("should return style templates adapter with correct structure", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper, }) expect(result.current).toHaveProperty("useData") expect(result.current).toHaveProperty("PreviewComponent") expect(result.current).toHaveProperty("getSortValue") expect(result.current).toHaveProperty("getSearchableText") expect(result.current).toHaveProperty("getGroupValue") expect(result.current).toHaveProperty("favoriteType", "template") }) describe("useData", () => { it("should return style templates data", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) const { result: dataResult } = renderHook(() => result.current.useData()) expect(dataResult.current.loading).toBe(false) expect(dataResult.current.error).toBe(null) expect(dataResult.current.items).toHaveLength(3) expect(dataResult.current.items[0].id).toBe("intro-fade") expect(dataResult.current.items[1].id).toBe("title-bounce") expect(dataResult.current.items[2].id).toBe("outro-minimal") }) }) describe("getSortValue", () => { const testTemplate = { id: "intro-fade", name: "Плавное появление", description: "Плавная анимация", category: "intro", style: "modern", aspectRatio: "16:9", duration: 2, hasAnimation: true, } it("should sort by different fields", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.getSortValue(testTemplate, "name")).toBe("плавное появление") expect(result.current.getSortValue(testTemplate, "category")).toBe("intro") expect(result.current.getSortValue(testTemplate, "style")).toBe("modern") expect(result.current.getSortValue(testTemplate, "duration")).toBe(2) expect(result.current.getSortValue(testTemplate, "aspectRatio")).toBe("16:9") expect(result.current.getSortValue(testTemplate, "unknown")).toBe("плавное появление") // Проверяем что mock был вызван с правильными параметрами expect(mockGetSortValue).toHaveBeenCalledWith(testTemplate, "name") }) }) describe("getSearchableText", () => { const testTemplate = { id: "intro-fade", name: "Плавное появление", description: "Плавная анимация появления", category: "intro", style: "modern", aspectRatio: "16:9", duration: 2, hasAnimation: true, tags: ["вступление", "плавное", "intro", "fade"], } it("should return searchable text array", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) const searchableText = result.current.getSearchableText(testTemplate) expect(searchableText).toContain("Плавное появление") expect(searchableText).toContain("Плавная анимация появления") expect(searchableText).toContain("intro") expect(searchableText).toContain("modern") expect(searchableText).toContain("16:9") expect(searchableText).toContain("вступление") expect(searchableText).toContain("плавное") expect(searchableText).toContain("fade") // Проверяем что mock был вызван expect(mockGetSearchableText).toHaveBeenCalledWith(testTemplate) }) }) describe("getGroupValue", () => { const testTemplate = { id: "intro-fade", name: "Плавное появление", category: "intro", style: "modern", aspectRatio: "16:9", duration: 2, hasAnimation: true, hasText: false, } it("should group by category", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.getGroupValue(testTemplate, "category")).toBe("intro") expect(result.current.getGroupValue(testTemplate, "duration")).toBe("Короткие (≤3с)") expect(result.current.getGroupValue(testTemplate, "unknown")).toBe("") }) it("should group by duration ranges", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) const shortTemplate = { ...testTemplate, duration: 0.5 } const mediumTemplate = { ...testTemplate, duration: 5 } const longTemplate = { ...testTemplate, duration: 15 } expect(result.current.getGroupValue(shortTemplate, "duration")).toBe("Короткие (≤3с)") expect(result.current.getGroupValue(mediumTemplate, "duration")).toBe("Средние (3-10с)") expect(result.current.getGroupValue(longTemplate, "duration")).toBe("Длинные (>10с)") }) it("should group by style", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.getGroupValue(testTemplate, "style")).toBe("modern") expect(result.current.getGroupValue({ ...testTemplate, style: null }, "style")).toBe("other") }) it("should group by aspect ratio", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.getGroupValue(testTemplate, "aspectRatio")).toBe("16:9") expect(result.current.getGroupValue({ ...testTemplate, aspectRatio: null }, "aspectRatio")).toBe("16:9") }) it("should group by features", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) const withTextAndAnimation = { ...testTemplate, hasText: true, hasAnimation: true } const withTextOnly = { ...testTemplate, hasText: true, hasAnimation: false } const withAnimationOnly = { ...testTemplate, hasText: false, hasAnimation: true } const basic = { ...testTemplate, hasText: false, hasAnimation: false } expect(result.current.getGroupValue(withTextAndAnimation, "features")).toBe("Текст + анимация") expect(result.current.getGroupValue(withTextOnly, "features")).toBe("С текстом") expect(result.current.getGroupValue(withAnimationOnly, "features")).toBe("С анимацией") expect(result.current.getGroupValue(basic, "features")).toBe("Базовые") }) }) describe("matchesFilter", () => { const introTemplate = { id: "intro-fade", name: "Плавное появление", category: "intro", style: "modern", aspectRatio: "16:9", duration: 2, hasAnimation: true, hasText: false, } const titleTemplate = { id: "title-bounce", name: "Подпрыгивающий заголовок", category: "title", style: "creative", aspectRatio: "16:9", duration: 1.5, hasAnimation: true, hasText: true, } it("should match filter by category", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.matchesFilter?.(introTemplate, "intro")).toBe(true) expect(result.current.matchesFilter?.(titleTemplate, "intro")).toBe(false) expect(result.current.matchesFilter?.(titleTemplate, "title")).toBe(true) expect(result.current.matchesFilter?.(introTemplate, "title")).toBe(false) }) it("should match filter by style", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.matchesFilter?.(introTemplate, "modern")).toBe(true) expect(result.current.matchesFilter?.(titleTemplate, "modern")).toBe(false) expect(result.current.matchesFilter?.(titleTemplate, "creative")).toBe(true) expect(result.current.matchesFilter?.(introTemplate, "creative")).toBe(false) }) it("should match filter by aspect ratio", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) const verticalTemplate = { ...introTemplate, aspectRatio: "9:16" } const squareTemplate = { ...introTemplate, aspectRatio: "1:1" } expect(result.current.matchesFilter?.(introTemplate, "16:9")).toBe(true) expect(result.current.matchesFilter?.(verticalTemplate, "16:9")).toBe(false) expect(result.current.matchesFilter?.(verticalTemplate, "9:16")).toBe(true) expect(result.current.matchesFilter?.(squareTemplate, "1:1")).toBe(true) }) it("should match filter by features", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.matchesFilter?.(introTemplate, "hasAnimation")).toBe(true) expect(result.current.matchesFilter?.(titleTemplate, "hasText")).toBe(true) expect(result.current.matchesFilter?.(introTemplate, "hasText")).toBe(false) }) it("should return true for 'all' filter", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.matchesFilter?.(introTemplate, "all")).toBe(true) expect(result.current.matchesFilter?.(titleTemplate, "all")).toBe(true) }) it("should return true for unknown filter", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.matchesFilter?.(introTemplate, "unknown")).toBe(true) }) }) describe("PreviewComponent", () => { it("should be defined", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.PreviewComponent).toBeDefined() expect(typeof result.current.PreviewComponent).toBe("function") }) it("should render correctly in list mode", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) const PreviewComponent = result.current.PreviewComponent const mockTemplate = { id: "intro-fade", name: "Плавное появление", description: "Плавная анимация появления", category: "intro", style: "modern", aspectRatio: "16:9", duration: 2, hasAnimation: true, hasText: false, thumbnail: "/style-templates/fade-intro.png", tags: ["вступление", "плавное", "intro", "fade"], } const mockProps = { item: mockTemplate, size: 100, viewMode: "list" as const, onClick: vi.fn(), onDragStart: vi.fn(), isSelected: false, isFavorite: false, onToggleFavorite: vi.fn(), onAddToTimeline: vi.fn(), } expect(() => ).not.toThrow() }) it("should render correctly in grid mode", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) const PreviewComponent = result.current.PreviewComponent const mockTemplate = { id: "title-bounce", name: "Подпрыгивающий заголовок", description: "Анимация подпрыгивающего заголовка", category: "title", style: "creative", aspectRatio: "16:9", duration: 1.5, hasAnimation: true, hasText: true, thumbnail: "/style-templates/bounce-title.png", tags: ["заголовок", "анимация", "title", "bounce"], } const mockProps = { item: mockTemplate, size: 120, viewMode: "grid" as const, onClick: vi.fn(), onDragStart: vi.fn(), isSelected: false, isFavorite: false, onToggleFavorite: vi.fn(), onAddToTimeline: vi.fn(), } expect(() => ).not.toThrow() }) it("should handle thumbnails mode with dimensions", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) const PreviewComponent = result.current.PreviewComponent const mockTemplate = { id: "outro-minimal", name: "Минималистичное завершение", description: "Чистое минималистичное завершение", category: "outro", style: "minimal", aspectRatio: "9:16", duration: 15, hasAnimation: false, hasText: true, thumbnail: null, tags: ["завершение", "минимализм", "outro", "minimal"], } const mockProps = { item: mockTemplate, size: { width: 160, height: 90 }, viewMode: "thumbnails" as const, onClick: vi.fn(), onDragStart: vi.fn(), isSelected: true, isFavorite: true, onToggleFavorite: vi.fn(), onAddToTimeline: vi.fn(), } expect(() => ).not.toThrow() }) }) describe("favoriteType", () => { it("should be 'template'", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) expect(result.current.favoriteType).toBe("template") }) }) describe("isFavorite", () => { it("should check if style template is favorite", () => { const { result } = renderHook(() => useStyleTemplatesAdapter(), { wrapper: TestWrapper }) const testTemplate = { id: "intro-fade", name: "Плавное появление", description: "Плавная анимация появления", category: "intro", style: "modern", aspectRatio: "16:9", duration: 2, hasAnimation: true, hasText: false, thumbnail: "/style-templates/fade-intro.png", tags: ["вступление", "плавное", "intro", "fade"], } expect(result.current.isFavorite).toBeDefined() expect(typeof result.current.isFavorite).toBe("function") expect(result.current.isFavorite(testTemplate)).toBe(false) }) }) })