summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/timeline
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-08-13 17:05:17 +0200
committerMaze Winther <mazewinther@gmail.com>2025-08-13 17:05:17 +0200
commit0274fb662647630803598e01c975e6b1439922ab (patch)
tree5cb90f172e8c47cb8db6a2d24bd6fed2163ca08a /apps/web/src/components/editor/timeline
parent0ae09c9df5591e2d18991a9f98647826997fe740 (diff)
downloadopencut-0274fb662647630803598e01c975e6b1439922ab.tar.gz
cleanup
Diffstat (limited to 'apps/web/src/components/editor/timeline')
-rw-r--r--apps/web/src/components/editor/timeline/__tests__/TimelineToolbar.test.tsx217
-rw-r--r--apps/web/src/components/editor/timeline/index.tsx20
-rw-r--r--apps/web/src/components/editor/timeline/timeline-playhead.tsx2
3 files changed, 8 insertions, 231 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
deleted file mode 100644
index 9a8ef666..00000000
--- a/apps/web/src/components/editor/timeline/__tests__/TimelineToolbar.test.tsx
+++ /dev/null
@@ -1,217 +0,0 @@
-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 669d556c..d8261d9b 100644
--- a/apps/web/src/components/editor/timeline/index.tsx
+++ b/apps/web/src/components/editor/timeline/index.tsx
@@ -1,6 +1,5 @@
"use client";
-import React from "react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "../../ui/button";
import {
@@ -609,7 +608,11 @@ export function Timeline() {
onMouseEnter={() => setIsInTimeline(true)}
onMouseLeave={() => setIsInTimeline(false)}
>
- <TimelineToolbar zoomLevel={zoomLevel} setZoomLevel={setZoomLevel} seek={seek} />
+ <TimelineToolbar
+ zoomLevel={zoomLevel}
+ setZoomLevel={setZoomLevel}
+ seek={seek}
+ />
{/* Timeline Container */}
<div
@@ -959,7 +962,6 @@ function TimelineToolbar({
const { currentTime, duration, isPlaying, toggle } = usePlaybackStore();
const { toggleBookmark, isBookmarked } = useProjectStore();
- // Action handlers
const handleSplitSelected = () => {
if (selectedElements.length === 0) return;
let splitCount = 0;
@@ -1075,7 +1077,6 @@ function TimelineToolbar({
clearSelectedElements();
};
- // Zoom handlers
const handleZoomIn = () => {
setZoomLevel(Math.min(4, zoomLevel + 0.25));
};
@@ -1092,13 +1093,11 @@ function TimelineToolbar({
await toggleBookmark(currentTime);
};
- // Check if the current time is bookmarked
const currentBookmarked = isBookmarked(currentTime);
return (
<div className="border-b flex items-center justify-between px-2 py-1">
<div className="flex items-center gap-1 w-full">
<TooltipProvider delayDuration={500}>
- {/* Play/Pause Button */}
<Tooltip>
<TooltipTrigger asChild>
<Button
@@ -1118,31 +1117,26 @@ 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)} // Seek to timeline start position
+ onClick={() => seek(0)}
className="mr-2"
>
<SkipBack className="h-4 w-4" />
</Button>
</TooltipTrigger>
- <TooltipContent>
- Return to Start (Home / Enter)
- </TooltipContent>
+ <TooltipContent>Return to Start (Home / Enter)</TooltipContent>
</Tooltip>
<div className="w-px h-6 bg-border mx-1" />
- {/* Time Display */}
<div
className="text-xs text-muted-foreground font-mono px-2"
style={{ minWidth: "18ch", textAlign: "center" }}
>
{currentTime.toFixed(1)}s / {duration.toFixed(1)}s
</div>
- {/* Test Clip Button - for debugging */}
{tracks.length === 0 && (
<>
<div className="w-px h-6 bg-border mx-1" />
diff --git a/apps/web/src/components/editor/timeline/timeline-playhead.tsx b/apps/web/src/components/editor/timeline/timeline-playhead.tsx
index 48c60596..a1be5b26 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 React, { useRef, useState, useEffect } from "react";
+import { useRef, useState, useEffect } from "react";
import { TimelineTrack } from "@/types/timeline";
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
import { useTimelinePlayhead } from "@/hooks/use-timeline-playhead";