/**
* Comprehensive tests for use-group-hotkeys hook
*/
import { renderHook } from "@testing-library/react"
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
// Mock timeline-machine
vi.mock("../../services/timeline-machine", () => ({
timelineMachine: {},
}))
// Mock shortcuts registry
vi.mock("@/features/keyboard-shortcuts", () => ({
shortcutsRegistry: {
updateAction: vi.fn(),
},
}))
import { shortcutsRegistry } from "@/features/keyboard-shortcuts"
// Mock keyboard event
const mockKeyboardEvent = new KeyboardEvent("keydown", { key: "Enter" })
const mockHotkeyEvent = { keys: ["enter"], scope: "all", element: null }
// Создаем отдельный модуль для моков
const mockCreateGroup = vi.fn()
const mockUngroupClips = vi.fn()
const mockGetGroupByClip = vi.fn()
vi.mock("../../hooks/use-clip-groups", () => ({
useClipGroups: () => ({
createGroup: mockCreateGroup,
ungroupClips: mockUngroupClips,
getGroupByClip: mockGetGroupByClip,
groups: [],
groupManager: {},
addToGroup: vi.fn(),
removeFromGroup: vi.fn(),
toggleCollapse: vi.fn(),
lockGroup: vi.fn(),
renameGroup: vi.fn(),
setGroupColor: vi.fn(),
createNestedSequence: vi.fn(),
updateNestedSequence: vi.fn(),
breakApartSequence: vi.fn(),
isClipInGroup: vi.fn(),
getClipsInGroup: vi.fn(),
}),
}))
// Mock project data
const mockProject: TimelineProject = {
id: "project-1",
name: "Test Project",
sections: [
{
id: "section-1",
name: "Section 1",
tracks: [
{
id: "track-1",
clips: [
{
id: "clip-1",
name: "Clip 1",
trackId: "track-1",
startTime: 0,
duration: 5,
} as TimelineClip,
{
id: "clip-2",
name: "Clip 2",
trackId: "track-1",
startTime: 5,
duration: 5,
} as TimelineClip,
],
},
],
},
],
globalTracks: [
{
id: "global-track-1",
clips: [
{
id: "clip-3",
name: "Clip 3",
trackId: "global-track-1",
startTime: 0,
duration: 10,
} as TimelineClip,
],
},
],
} as any
const mockUiState = {
selectedClipIds: ["clip-1", "clip-2"],
}
const mockSend = vi.fn()
vi.mock("../../hooks/use-timeline", () => ({
useTimeline: () => ({
project: mockProject,
uiState: mockUiState,
send: mockSend,
}),
}))
import { useGroupHotkeys } from "../../hooks/use-group-hotkeys"
import type { ClipGroup } from "../../types/clip-groups"
import type { TimelineClip, TimelineProject } from "../../types/timeline"
import { MockTimelineProvider } from "../test-providers"
describe("useGroupHotkeys", () => {
beforeEach(() => {
vi.clearAllMocks()
// Сбрасываем выбранные клипы
mockUiState.selectedClipIds = ["clip-1", "clip-2"]
// Настройка мока getGroupByClip по умолчанию
mockGetGroupByClip.mockReturnValue({
id: "group-1",
name: "Test Group",
clips: [
{ clipId: "clip-1", trackId: "track-1" },
{ clipId: "clip-2", trackId: "track-1" },
],
} as ClipGroup)
})
afterEach(() => {
vi.clearAllTimers()
})
it("должен регистрировать горячие клавиши", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
renderHook(() => useGroupHotkeys(), { wrapper })
// Проверяем что были зарегистрированы actions для shortcuts
expect(vi.mocked(shortcutsRegistry).updateAction).toHaveBeenCalledWith("group-clips", expect.any(Function))
expect(vi.mocked(shortcutsRegistry).updateAction).toHaveBeenCalledWith("ungroup-clips", expect.any(Function))
})
it("должен создавать группу при нажатии Cmd+G с выбранными клипами", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
renderHook(() => useGroupHotkeys(), { wrapper })
// Получаем функцию, переданную для group-clips
const groupClipsAction = vi
.mocked(shortcutsRegistry)
.updateAction.mock.calls.find((call) => call[0] === "group-clips")?.[1]
expect(groupClipsAction).toBeDefined()
// Симулируем вызов action
groupClipsAction?.(mockKeyboardEvent, mockHotkeyEvent as any)
expect(mockCreateGroup).toHaveBeenCalledWith([
mockProject.sections[0].tracks[0].clips[0],
mockProject.sections[0].tracks[0].clips[1],
])
})
it("не должен создавать группу если выбрано меньше 2 клипов", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
// Изменяем выбранные клипы
mockUiState.selectedClipIds = ["clip-1"]
renderHook(() => useGroupHotkeys(), { wrapper })
const groupClipsAction = vi
.mocked(shortcutsRegistry)
.updateAction.mock.calls.find((call) => call[0] === "group-clips")?.[1]
expect(groupClipsAction).toBeDefined()
groupClipsAction?.(mockKeyboardEvent, mockHotkeyEvent as any)
expect(mockCreateGroup).not.toHaveBeenCalled()
})
it("должен разгруппировывать клипы при нажатии Cmd+Shift+G", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
renderHook(() => useGroupHotkeys(), { wrapper })
// Получаем функцию для ungroup-clips
const ungroupClipsAction = vi
.mocked(shortcutsRegistry)
.updateAction.mock.calls.find((call) => call[0] === "ungroup-clips")?.[1]
expect(ungroupClipsAction).toBeDefined()
ungroupClipsAction?.(mockKeyboardEvent, mockHotkeyEvent as any)
expect(mockGetGroupByClip).toHaveBeenCalledWith("clip-1")
expect(mockUngroupClips).toHaveBeenCalledWith("group-1")
})
it("не должен разгруппировывать если клип не в группе", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
// Мокаем что клип не в группе
mockGetGroupByClip.mockReturnValue(undefined)
renderHook(() => useGroupHotkeys(), { wrapper })
const ungroupClipsAction = vi
.mocked(shortcutsRegistry)
.updateAction.mock.calls.find((call) => call[0] === "ungroup-clips")?.[1]
expect(ungroupClipsAction).toBeDefined()
ungroupClipsAction?.(mockKeyboardEvent, mockHotkeyEvent as any)
expect(mockGetGroupByClip).toHaveBeenCalledWith("clip-1")
expect(mockUngroupClips).not.toHaveBeenCalled()
})
it("не должен разгруппировывать если нет выбранных клипов", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
// Очищаем выбранные клипы
mockUiState.selectedClipIds = []
renderHook(() => useGroupHotkeys(), { wrapper })
const ungroupClipsAction = vi
.mocked(shortcutsRegistry)
.updateAction.mock.calls.find((call) => call[0] === "ungroup-clips")?.[1]
expect(ungroupClipsAction).toBeDefined()
ungroupClipsAction?.(mockKeyboardEvent, mockHotkeyEvent as any)
expect(mockGetGroupByClip).not.toHaveBeenCalled()
expect(mockUngroupClips).not.toHaveBeenCalled()
})
it("должен работать с клипами из глобальных треков", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
// Выбираем клипы включая глобальный трек
mockUiState.selectedClipIds = ["clip-1", "clip-3"]
renderHook(() => useGroupHotkeys(), { wrapper })
const groupClipsAction = vi
.mocked(shortcutsRegistry)
.updateAction.mock.calls.find((call) => call[0] === "group-clips")?.[1]
expect(groupClipsAction).toBeDefined()
groupClipsAction?.(mockKeyboardEvent, mockHotkeyEvent as any)
// Проверяем что createGroup был вызван с правильными клипами
// Порядок: сначала globalTracks, потом sections
expect(mockCreateGroup).toHaveBeenCalledWith([
mockProject.globalTracks[0].clips[0], // clip-3
mockProject.sections[0].tracks[0].clips[0], // clip-1
])
})
it("должен работать с разными сочетаниями клавиш для разных ОС", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
renderHook(() => useGroupHotkeys(), { wrapper })
// Проверяем что зарегистрированы shortcuts
expect(vi.mocked(shortcutsRegistry).updateAction).toHaveBeenCalledWith("group-clips", expect.any(Function))
expect(vi.mocked(shortcutsRegistry).updateAction).toHaveBeenCalledWith("ungroup-clips", expect.any(Function))
})
it("должен отключать хоткеи на элементах форм", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
renderHook(() => useGroupHotkeys(), { wrapper })
// В новой системе shortcuts это контролируется контекстом,
// проверяем что shortcuts были зарегистрированы
expect(vi.mocked(shortcutsRegistry).updateAction).toHaveBeenCalled()
})
it("должен корректно обрабатывать отсутствие проекта", () => {
// Временно сохраняем оригинальный проект и устанавливаем null
const originalProject = mockProject
// Очищаем глобальные переменные для имитации отсутствия проекта
mockProject.sections = []
mockProject.globalTracks = []
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
renderHook(() => useGroupHotkeys(), { wrapper })
const groupClipsAction = vi
.mocked(shortcutsRegistry)
.updateAction.mock.calls.find((call) => call[0] === "group-clips")?.[1]
expect(groupClipsAction).toBeDefined()
groupClipsAction?.(mockKeyboardEvent, mockHotkeyEvent as any)
// Не должно быть вызовов createGroup когда нет клипов
expect(mockCreateGroup).not.toHaveBeenCalled()
// Восстанавливаем проект
mockProject.sections = originalProject.sections
mockProject.globalTracks = originalProject.globalTracks
})
it("должен выбирать первый клип из выбранных для определения группы", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
// Убедимся что в проекте есть нужные клипы после предыдущего теста
if (mockProject.sections.length === 0) {
mockProject.sections = [
{
id: "section-1",
name: "Section 1",
tracks: [
{
id: "track-1",
clips: [
{
id: "clip-1",
name: "Clip 1",
trackId: "track-1",
startTime: 0,
duration: 5,
} as TimelineClip,
{
id: "clip-2",
name: "Clip 2",
trackId: "track-1",
startTime: 5,
duration: 5,
} as TimelineClip,
],
},
],
},
] as any
}
// Выбираем только клипы из sections
mockUiState.selectedClipIds = ["clip-2"]
// Настраиваем группу для клипа
mockGetGroupByClip.mockReturnValue({
id: "group-2",
name: "Group 2",
clips: [{ clipId: "clip-2", trackId: "track-1" }],
} as ClipGroup)
renderHook(() => useGroupHotkeys(), { wrapper })
const ungroupClipsAction = vi
.mocked(shortcutsRegistry)
.updateAction.mock.calls.find((call) => call[0] === "ungroup-clips")?.[1]
expect(ungroupClipsAction).toBeDefined()
ungroupClipsAction?.(mockKeyboardEvent, mockHotkeyEvent as any)
// Должна использоваться группа первого найденного клипа
expect(mockGetGroupByClip).toHaveBeenCalledWith("clip-2")
expect(mockUngroupClips).toHaveBeenCalledWith("group-2")
})
it("должен собирать клипы из всех секций", () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
)
// Убедимся что проект восстановлен
if (mockProject.sections.length === 0 || !mockProject.globalTracks) {
mockProject.sections = [
{
id: "section-1",
name: "Section 1",
tracks: [
{
id: "track-1",
clips: [
{
id: "clip-1",
name: "Clip 1",
trackId: "track-1",
startTime: 0,
duration: 5,
} as TimelineClip,
{
id: "clip-2",
name: "Clip 2",
trackId: "track-1",
startTime: 5,
duration: 5,
} as TimelineClip,
],
},
],
},
] as any
mockProject.globalTracks = [
{
id: "global-track-1",
clips: [
{
id: "clip-3",
name: "Clip 3",
trackId: "global-track-1",
startTime: 0,
duration: 10,
} as TimelineClip,
],
},
] as any
}
// Добавляем дополнительную секцию
const originalSections = [...mockProject.sections]
mockProject.sections.push({
id: "section-2",
name: "Section 2",
tracks: [
{
id: "track-2",
clips: [
{
id: "clip-4",
name: "Clip 4",
trackId: "track-2",
startTime: 0,
duration: 5,
} as TimelineClip,
],
},
],
} as any)
// Выбираем клипы из разных секций
mockUiState.selectedClipIds = ["clip-1", "clip-4"]
renderHook(() => useGroupHotkeys(), { wrapper })
const groupClipsAction = vi
.mocked(shortcutsRegistry)
.updateAction.mock.calls.find((call) => call[0] === "group-clips")?.[1]
expect(groupClipsAction).toBeDefined()
groupClipsAction?.(mockKeyboardEvent, mockHotkeyEvent as any)
// Проверяем что createGroup был вызван
expect(mockCreateGroup).toHaveBeenCalled()
// Восстанавливаем оригинальные секции
mockProject.sections = originalSections
})
})