summaryrefslogtreecommitdiff
path: root/apps/web/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components')
-rw-r--r--apps/web/src/components/editor/timeline/__tests__/TimelineToolbar.test.tsx217
-rw-r--r--apps/web/src/components/editor/timeline/index.tsx4
-rw-r--r--apps/web/src/components/editor/timeline/timeline-playhead.tsx2
3 files changed, 221 insertions, 2 deletions
diff --git a/apps/web/src/components/editor/timeline/__tests__/TimelineToolbar.test.tsx b/apps/web/src/components/editor/timeline/__tests__/TimelineToolbar.test.tsx
new file mode 100644
index 00000000..9a8ef666
--- /dev/null
+++ b/apps/web/src/components/editor/timeline/__tests__/TimelineToolbar.test.tsx
@@ -0,0 +1,217 @@
+import React from 'react'
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { render, screen, fireEvent } from '@testing-library/react'
+import { Timeline } from '../index'
+
+// Mock the stores and hooks
+vi.mock('@/stores/timeline-store', () => ({
+ useTimelineStore: () => ({
+ tracks: [],
+ addTrack: vi.fn(),
+ addElementToTrack: vi.fn(),
+ removeElementFromTrack: vi.fn(),
+ removeElementFromTrackWithRipple: vi.fn(),
+ selectedElements: [],
+ clearSelectedElements: vi.fn(),
+ splitElement: vi.fn(),
+ splitAndKeepLeft: vi.fn(),
+ splitAndKeepRight: vi.fn(),
+ separateAudio: vi.fn(),
+ snappingEnabled: false,
+ toggleSnapping: vi.fn(),
+ deleteElements: vi.fn(),
+ duplicateElement: vi.fn(),
+ selectAll: vi.fn(),
+ zoomLevel: 1,
+ setZoomLevel: vi.fn(),
+ dragState: { isDragging: false, draggedElement: null, dragOffset: { x: 0, y: 0 } },
+ getTotalDuration: () => 100,
+ setSelectedElements: vi.fn(),
+ toggleTrackMute: vi.fn(),
+ })
+}))
+
+vi.mock('@/stores/media-store', () => ({
+ useMediaStore: () => ({
+ mediaItems: [],
+ addMediaItem: vi.fn(),
+ })
+}))
+
+vi.mock('@/stores/project-store', () => ({
+ useProjectStore: () => ({
+ activeProject: null,
+ })
+}))
+
+const mockSeek = vi.fn()
+
+vi.mock('@/stores/playback-store', () => ({
+ usePlaybackStore: () => ({
+ currentTime: 0,
+ duration: 100,
+ seek: mockSeek,
+ setDuration: vi.fn(),
+ isPlaying: false,
+ toggle: vi.fn(),
+ }),
+}))
+
+// Mock the timeline playhead hook
+vi.mock('@/hooks/use-timeline-playhead', () => ({
+ useTimelinePlayhead: () => ({
+ playheadRef: { current: null },
+ updatePlayheadPosition: vi.fn(),
+ onPlayheadDrag: vi.fn(),
+ })
+}))
+
+vi.mock('@/hooks/use-editor-actions', () => ({
+ useEditorActions: () => ({})
+}))
+
+vi.mock('@/hooks/use-timeline-context-menu', () => ({
+ useTimelineContextMenu: () => ({
+ contextMenu: null,
+ handleContextMenu: vi.fn(),
+ closeContextMenu: vi.fn(),
+ })
+}))
+
+vi.mock('@/hooks/use-timeline-selection', () => ({
+ useTimelineSelection: () => ({
+ isSelecting: false,
+ selectionBox: null,
+ handleMouseDown: vi.fn(),
+ })
+}))
+
+vi.mock('@/hooks/use-timeline-drag-drop', () => ({
+ useTimelineDragDrop: () => ({
+ dragProps: {},
+ })
+}))
+
+// Mock UI components to avoid dependency issues
+vi.mock('../../ui/tooltip', () => ({
+ Tooltip: ({ children }: any) => children,
+ TooltipTrigger: ({ children }: any) => children,
+ TooltipContent: ({ children }: any) => <div>{children}</div>,
+ TooltipProvider: ({ children }: any) => children,
+}))
+
+vi.mock('../../ui/button', () => ({
+ Button: ({ children, onClick, ...props }: any) => (
+ <button onClick={onClick} {...props} aria-label={props['aria-label']}>
+ {children}
+ </button>
+ ),
+}))
+
+vi.mock('../../ui/scroll-area', () => ({
+ ScrollArea: ({ children }: any) => <div>{children}</div>,
+}))
+
+vi.mock('lucide-react', () => ({
+ SkipBack: () => <svg data-testid="skip-back-icon" />,
+ Play: () => <svg data-testid="play-icon" />,
+ Pause: () => <svg data-testid="pause-icon" />,
+ Scissors: () => <svg />,
+ ArrowLeftToLine: () => <svg />,
+ ArrowRightToLine: () => <svg />,
+ Trash2: () => <svg />,
+ Snowflake: () => <svg />,
+ Copy: () => <svg />,
+ SplitSquareHorizontal: () => <svg />,
+ Video: () => <svg />,
+ Music: () => <svg />,
+ TypeIcon: () => <svg />,
+ Magnet: () => <svg />,
+ Link: () => <svg />,
+ ZoomIn: () => <svg />,
+ ZoomOut: () => <svg />,
+}))
+
+vi.mock('@/constants/timeline', () => ({
+ TIMELINE_CONSTANTS: {
+ DEFAULT_TEXT_DURATION: 5,
+ }
+}))
+
+vi.mock('@/lib/utils', () => ({
+ cn: (...args: any[]) => args.join(' '),
+ snapTimeToFrame: (time: number) => time,
+}))
+
+// Mock timeline utilities
+vi.mock('@/lib/timeline-utils', () => ({
+ getTotalDuration: () => 100,
+ getCurrentSnapPoint: () => null,
+}))
+
+// Mock context menu components
+vi.mock('../../ui/context-menu', () => ({
+ ContextMenu: ({ children }: any) => children,
+ ContextMenuTrigger: ({ children }: any) => children,
+ ContextMenuContent: ({ children }: any) => <div>{children}</div>,
+ ContextMenuItem: ({ children }: any) => <div>{children}</div>,
+ ContextMenuSeparator: () => <div />,
+}))
+
+// Mock dialog components
+vi.mock('../../ui/dialog', () => ({
+ Dialog: ({ children }: any) => children,
+ DialogContent: ({ children }: any) => <div>{children}</div>,
+ DialogHeader: ({ children }: any) => <div>{children}</div>,
+ DialogTitle: ({ children }: any) => <div>{children}</div>,
+}))
+
+describe('Timeline - Return to Start Button', () => {
+ beforeEach(() => {
+ mockSeek.mockClear()
+ })
+
+ it('should render Return to Start button', () => {
+ render(<Timeline />)
+
+ // Look for the SkipBack icon which indicates our button
+ const skipBackIcon = screen.getByTestId('skip-back-icon')
+ expect(skipBackIcon).toBeInTheDocument()
+ })
+
+ it('should call seek(0) when Return to Start button is clicked', () => {
+ render(<Timeline />)
+
+ // Find the button that contains the SkipBack icon
+ const skipBackIcon = screen.getByTestId('skip-back-icon')
+ const button = skipBackIcon.closest('button')
+
+ expect(button).toBeInTheDocument()
+ fireEvent.click(button!)
+
+ expect(mockSeek).toHaveBeenCalledWith(0)
+ })
+
+ it('should have tooltip structure for Return to Start button', () => {
+ render(<Timeline />)
+
+ // Find the skip back button and verify it's wrapped in tooltip components
+ const skipBackIcon = screen.getByTestId('skip-back-icon')
+ const button = skipBackIcon.closest('button')
+
+ expect(button).toBeInTheDocument()
+ // The button should exist and be clickable, which means the tooltip structure is in place
+ expect(button).toHaveAttribute('data-state', 'closed') // Tooltip trigger state
+ })
+
+ it('should have SkipBack icon in Return to Start button', () => {
+ render(<Timeline />)
+
+ const skipBackIcon = screen.getByTestId('skip-back-icon')
+ expect(skipBackIcon).toBeInTheDocument()
+
+ // Verify it's inside a button
+ const button = skipBackIcon.closest('button')
+ expect(button).toBeInTheDocument()
+ })
+}) \ No newline at end of file
diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx
index cf5fd524..0d9660bb 100644
--- a/apps/web/src/components/editor/timeline/index.tsx
+++ b/apps/web/src/components/editor/timeline/index.tsx
@@ -1,5 +1,6 @@
"use client";
+import React from "react";
import { ScrollArea } from "../../ui/scroll-area";
import { Button } from "../../ui/button";
import {
@@ -984,12 +985,13 @@ function TimelineToolbar({
{isPlaying ? "Pause (Space)" : "Play (Space)"}
</TooltipContent>
</Tooltip>
+ {/* Return to Start Button - Seeks timeline to 00:00:00 */}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="text"
size="icon"
- onClick={() => seek(0)}
+ onClick={() => seek(0)} // Seek to timeline start position
className="mr-2"
>
<SkipBack className="h-4 w-4" />
diff --git a/apps/web/src/components/editor/timeline/timeline-playhead.tsx b/apps/web/src/components/editor/timeline/timeline-playhead.tsx
index b33166c0..dbbd18df 100644
--- a/apps/web/src/components/editor/timeline/timeline-playhead.tsx
+++ b/apps/web/src/components/editor/timeline/timeline-playhead.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useRef, useState, useEffect } from "react";
+import React, { useRef, useState, useEffect } from "react";
import { TimelineTrack } from "@/types/timeline";
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
import { useTimelinePlayhead } from "@/hooks/use-timeline-playhead";