From d133ac8b205a221b828b9308346c9f92dd8d61d9 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Nov 2025 03:53:07 +0000 Subject: [PATCH 1/3] Add automatic timer logging feature for tasks Implemented a timer system that allows users to start/stop timers for tasks instead of manually logging in 15 minute increments. This addresses issue #38. Changes: - Extended TimeState to track active timer (taskId, startTime, date) - Modified startTime action to set activeTimer and prevent multiple concurrent timers - Modified stopTime action to use activeTimer and automatically log elapsed time - Added cancelTimer action to cancel timer without logging - Created TimerButton component with real-time elapsed time display - Integrated timer button into TaskRow component (both view and edit modes) - Added Timer column header to table layout Features: - Start/Stop timer buttons for each task - Real-time elapsed time display (HH:MM:SS) - Only one timer can run at a time (others are disabled) - Timer state persists in Redux store and localStorage - Automatic time logging when timer is stopped --- src/app/slices/timeSlice.ts | 62 ++++++++++++++++++----- src/components/layout/Table.tsx | 1 + src/components/task/TaskRow.tsx | 7 +++ src/components/time/TimerButton.tsx | 77 +++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 src/components/time/TimerButton.tsx diff --git a/src/app/slices/timeSlice.ts b/src/app/slices/timeSlice.ts index b7d22ba..2e84500 100644 --- a/src/app/slices/timeSlice.ts +++ b/src/app/slices/timeSlice.ts @@ -8,6 +8,11 @@ import { START_HOUR } from '../constants'; export interface TimeState { dateTimes: DateTimes[]; + activeTimer?: { + taskId: number; + startTime: number; + date: number; + }; } export interface DateTimes { @@ -30,6 +35,7 @@ const initialState: TimeState = { taskTimes: [], }, ], + activeTimer: undefined, }; export const timeSlice = createSlice({ @@ -58,37 +64,66 @@ export const timeSlice = createSlice({ taskId: number; }> ) => { + // Only allow one timer at a time + if (state.activeTimer) { + return; + } + const dateTimes = state.dateTimes.find( (dateTimes) => dateTimes.date === action.payload.date ); if (dateTimes) { + const startTime = Date.now(); dateTimes.taskTimes.push({ task: action.payload.taskId, - start: Date.now(), + start: startTime, }); + state.activeTimer = { + taskId: action.payload.taskId, + startTime: startTime, + date: action.payload.date, + }; } }, - stopTime: ( - state, - action: PayloadAction<{ - date: number; - taskId: number; - start: number; - }> - ) => { + stopTime: (state) => { + if (!state.activeTimer) { + return; + } + const dateTimes = state.dateTimes.find( - (dateTimes) => dateTimes.date === action.payload.date + (dateTimes) => dateTimes.date === state.activeTimer!.date ); if (dateTimes) { const taskTime = dateTimes.taskTimes.find( (taskTime) => - taskTime.task === action.payload.taskId && - taskTime.start === action.payload.start + taskTime.task === state.activeTimer!.taskId && + taskTime.start === state.activeTimer!.startTime ); if (taskTime) { taskTime.end = Date.now(); } } + state.activeTimer = undefined; + }, + cancelTimer: (state) => { + if (!state.activeTimer) { + return; + } + + const dateTimes = state.dateTimes.find( + (dateTimes) => dateTimes.date === state.activeTimer!.date + ); + if (dateTimes) { + const taskTimeIndex = dateTimes.taskTimes.findIndex( + (taskTime) => + taskTime.task === state.activeTimer!.taskId && + taskTime.start === state.activeTimer!.startTime + ); + if (taskTimeIndex !== -1) { + dateTimes.taskTimes.splice(taskTimeIndex, 1); + } + } + state.activeTimer = undefined; }, recordTime: ( state, @@ -174,6 +209,7 @@ export const { createDate, startTime, stopTime, + cancelTimer, recordTime, removeTime, toggleSegment, @@ -247,4 +283,6 @@ export const getTimesForTask = createSelector( } ); +export const selectActiveTimer = (state: RootState) => state.time.activeTimer; + export default timeSlice.reducer; diff --git a/src/components/layout/Table.tsx b/src/components/layout/Table.tsx index 30eed17..5a7cf7f 100644 --- a/src/components/layout/Table.tsx +++ b/src/components/layout/Table.tsx @@ -61,6 +61,7 @@ export const Table = () => { Task + Timer Total diff --git a/src/components/task/TaskRow.tsx b/src/components/task/TaskRow.tsx index f677374..783d404 100644 --- a/src/components/task/TaskRow.tsx +++ b/src/components/task/TaskRow.tsx @@ -4,6 +4,7 @@ import { TableRow, Input, Button, TableCell } from '@mui/material'; import HighlightOffIcon from '@mui/icons-material/HighlightOff'; import { TimeRowCell } from '../time/TimeRow'; import { TimeSummaryCell } from '../time/TimeSummaryCell'; +import { TimerButton } from '../time/TimerButton'; import { useAppDispatch, useAppSelector } from '../../app/hooks'; import { deleteTask, getTask, updateTask } from '../../app/slices/taskSlice'; import { @@ -144,6 +145,9 @@ export const TaskRow = (props: TaskRowProps) => { > {task.description} + + + {taskRowTime()} ); @@ -180,6 +184,9 @@ export const TaskRow = (props: TaskRowProps) => { Update Task + + + {taskRowTime()} ); diff --git a/src/components/time/TimerButton.tsx b/src/components/time/TimerButton.tsx new file mode 100644 index 0000000..aaf1b5e --- /dev/null +++ b/src/components/time/TimerButton.tsx @@ -0,0 +1,77 @@ +import { useEffect, useState } from 'react'; +import { Button } from '@mui/material'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import StopIcon from '@mui/icons-material/Stop'; +import { useAppDispatch, useAppSelector } from '../../app/hooks'; +import { + startTime, + stopTime, + selectActiveTimer, +} from '../../app/slices/timeSlice'; + +interface TimerButtonProps { + taskId: number; + date: number; +} + +export const TimerButton = ({ taskId, date }: TimerButtonProps) => { + const dispatch = useAppDispatch(); + const activeTimer = useAppSelector(selectActiveTimer); + const [elapsedTime, setElapsedTime] = useState(0); + + const isActiveForThisTask = activeTimer?.taskId === taskId; + const isActiveForOtherTask = activeTimer && !isActiveForThisTask; + + // Update elapsed time every second when timer is active for this task + useEffect(() => { + if (!isActiveForThisTask || !activeTimer) { + setElapsedTime(0); + return; + } + + const updateElapsed = () => { + const elapsed = Date.now() - activeTimer.startTime; + setElapsedTime(elapsed); + }; + + // Update immediately + updateElapsed(); + + // Then update every second + const interval = setInterval(updateElapsed, 1000); + return () => clearInterval(interval); + }, [isActiveForThisTask, activeTimer]); + + const formatElapsedTime = (ms: number): string => { + const totalSeconds = Math.floor(ms / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + }; + + const handleClick = () => { + if (isActiveForThisTask) { + dispatch(stopTime()); + } else { + dispatch(startTime({ date, taskId })); + } + }; + + return ( + + ); +}; From 5eda2ac74e17c2242d0369f3fbd2d5a8b304dc1b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 00:09:35 +0000 Subject: [PATCH 2/3] Add comprehensive test coverage for timer feature Added test coverage for the automatic timer logging feature including: - Updated timeSlice tests for new timer API (startTime, stopTime, cancelTimer) - Added tests for activeTimer state tracking - Created TimerButton component tests covering: - Start/stop timer functionality - Elapsed time display - Disabled state when another task has active timer - UI styling (contained error vs outlined primary) - State updates in Redux store All 225 tests passing including 7 new TimerButton tests. --- src/app/slices/timeSlice.test.ts | 209 +++++++++++++++++++++-- src/components/time/TimerButton.test.tsx | 195 +++++++++++++++++++++ 2 files changed, 386 insertions(+), 18 deletions(-) create mode 100644 src/components/time/TimerButton.test.tsx diff --git a/src/app/slices/timeSlice.test.ts b/src/app/slices/timeSlice.test.ts index 2b7ac57..d9ae83c 100644 --- a/src/app/slices/timeSlice.test.ts +++ b/src/app/slices/timeSlice.test.ts @@ -3,12 +3,14 @@ import timeReducer, { createDate, startTime, stopTime, + cancelTimer, recordTime, removeTime, toggleSegment, getSegment, getTimesForDate, getTimesForTask, + selectActiveTimer, type TimeState, type TaskTime, } from './timeSlice'; @@ -19,6 +21,7 @@ import type { RootState } from '../store'; describe('timeSlice', () => { const initialState: TimeState = { dateTimes: [], + activeTimer: undefined, }; describe('createDate', () => { @@ -46,9 +49,10 @@ describe('timeSlice', () => { }); describe('startTime', () => { - it('should add a time entry without an end time', () => { + it('should add a time entry without an end time and set activeTimer', () => { const stateWithDate: TimeState = { dateTimes: [{ date: mockToday, taskTimes: [] }], + activeTimer: undefined, }; const action = startTime({ date: mockToday, taskId: 1 }); @@ -60,6 +64,12 @@ describe('timeSlice', () => { start: expect.any(Number), }); expect(newState.dateTimes[0].taskTimes[0].end).toBeUndefined(); + expect(newState.activeTimer).toBeDefined(); + expect(newState.activeTimer?.taskId).toBe(1); + expect(newState.activeTimer?.date).toBe(mockToday); + expect(newState.activeTimer?.startTime).toBe( + newState.dateTimes[0].taskTimes[0].start + ); }); it('should not add time if date does not exist', () => { @@ -67,11 +77,36 @@ describe('timeSlice', () => { const newState = timeReducer(initialState, action); expect(newState.dateTimes).toHaveLength(0); + expect(newState.activeTimer).toBeUndefined(); + }); + + it('should not start a timer if another timer is already active', () => { + const existingStartTime = Date.now() - 5000; + const stateWithActiveTimer: TimeState = { + dateTimes: [ + { + date: mockToday, + taskTimes: [{ task: 1, start: existingStartTime }], + }, + ], + activeTimer: { + taskId: 1, + startTime: existingStartTime, + date: mockToday, + }, + }; + + const action = startTime({ date: mockToday, taskId: 2 }); + const newState = timeReducer(stateWithActiveTimer, action); + + // Should not change state + expect(newState.dateTimes[0].taskTimes).toHaveLength(1); + expect(newState.activeTimer?.taskId).toBe(1); }); }); describe('stopTime', () => { - it('should add end time to an existing time entry', () => { + it('should add end time to an existing time entry and clear activeTimer', () => { const startTimestamp = new Date('2024-01-15T10:00:00').getTime(); const stateWithStartedTime: TimeState = { dateTimes: [ @@ -80,45 +115,136 @@ describe('timeSlice', () => { taskTimes: [{ task: 1, start: startTimestamp }], }, ], + activeTimer: { + taskId: 1, + startTime: startTimestamp, + date: mockToday, + }, }; - const action = stopTime({ - date: mockToday, - taskId: 1, - start: startTimestamp, - }); + const action = stopTime(); const newState = timeReducer(stateWithStartedTime, action); expect(newState.dateTimes[0].taskTimes[0].end).toBeDefined(); expect(newState.dateTimes[0].taskTimes[0].end).toBeGreaterThan( startTimestamp ); + expect(newState.activeTimer).toBeUndefined(); + }); + + it('should not modify state if no active timer', () => { + const stateWithDate: TimeState = { + dateTimes: [{ date: mockToday, taskTimes: [] }], + activeTimer: undefined, + }; + + const action = stopTime(); + const newState = timeReducer(stateWithDate, action); + + expect(newState.dateTimes[0].taskTimes).toHaveLength(0); + expect(newState.activeTimer).toBeUndefined(); }); it('should not modify state if date does not exist', () => { - const action = stopTime({ - date: mockToday, - taskId: 1, - start: Date.now(), - }); - const newState = timeReducer(initialState, action); + const startTimestamp = Date.now(); + const stateWithActiveTimer: TimeState = { + dateTimes: [], + activeTimer: { + taskId: 1, + startTime: startTimestamp, + date: mockToday, + }, + }; + + const action = stopTime(); + const newState = timeReducer(stateWithActiveTimer, action); expect(newState.dateTimes).toHaveLength(0); + expect(newState.activeTimer).toBeUndefined(); }); it('should not modify state if task time does not exist', () => { + const startTimestamp = Date.now(); const stateWithDate: TimeState = { dateTimes: [{ date: mockToday, taskTimes: [] }], + activeTimer: { + taskId: 1, + startTime: startTimestamp, + date: mockToday, + }, }; - const action = stopTime({ - date: mockToday, - taskId: 1, - start: Date.now(), - }); + const action = stopTime(); const newState = timeReducer(stateWithDate, action); expect(newState.dateTimes[0].taskTimes).toHaveLength(0); + expect(newState.activeTimer).toBeUndefined(); + }); + }); + + describe('cancelTimer', () => { + it('should remove the active timer entry and clear activeTimer', () => { + const startTimestamp = Date.now() - 5000; + const stateWithActiveTimer: TimeState = { + dateTimes: [ + { + date: mockToday, + taskTimes: [{ task: 1, start: startTimestamp }], + }, + ], + activeTimer: { + taskId: 1, + startTime: startTimestamp, + date: mockToday, + }, + }; + + const action = cancelTimer(); + const newState = timeReducer(stateWithActiveTimer, action); + + expect(newState.dateTimes[0].taskTimes).toHaveLength(0); + expect(newState.activeTimer).toBeUndefined(); + }); + + it('should not modify state if no active timer', () => { + const stateWithDate: TimeState = { + dateTimes: [{ date: mockToday, taskTimes: [] }], + activeTimer: undefined, + }; + + const action = cancelTimer(); + const newState = timeReducer(stateWithDate, action); + + expect(newState.dateTimes[0].taskTimes).toHaveLength(0); + expect(newState.activeTimer).toBeUndefined(); + }); + + it('should not remove other time entries', () => { + const startTimestamp1 = Date.now() - 10000; + const startTimestamp2 = Date.now() - 5000; + const stateWithMultipleTimes: TimeState = { + dateTimes: [ + { + date: mockToday, + taskTimes: [ + { task: 1, start: startTimestamp1, end: Date.now() - 6000 }, + { task: 1, start: startTimestamp2 }, + ], + }, + ], + activeTimer: { + taskId: 1, + startTime: startTimestamp2, + date: mockToday, + }, + }; + + const action = cancelTimer(); + const newState = timeReducer(stateWithMultipleTimes, action); + + expect(newState.dateTimes[0].taskTimes).toHaveLength(1); + expect(newState.dateTimes[0].taskTimes[0].start).toBe(startTimestamp1); + expect(newState.activeTimer).toBeUndefined(); }); }); @@ -423,6 +549,7 @@ describe('timeSlice', () => { it('should handle date without entry', () => { const state: TimeState = { dateTimes: [], + activeTimer: undefined, }; const result = getSegment(state, mockToday, 1, 0); @@ -456,6 +583,7 @@ describe('timeSlice', () => { taskTimes: [{ task: 1, start, end }], }, ], + activeTimer: undefined, }; const result = getSegment(state, mockToday, 1, 0); @@ -481,6 +609,7 @@ describe('timeSlice', () => { const state: TimeState = { dateTimes: [{ date: mockToday, taskTimes }], + activeTimer: undefined, }; const result = getTimesForDate(state, mockToday); @@ -513,6 +642,7 @@ describe('timeSlice', () => { const state: RootState = { time: { dateTimes: [{ date: mockToday, taskTimes }], + activeTimer: undefined, }, } as RootState; @@ -527,6 +657,7 @@ describe('timeSlice', () => { const state: RootState = { time: { dateTimes: [], + activeTimer: undefined, }, } as unknown as RootState; @@ -547,6 +678,7 @@ describe('timeSlice', () => { const state: RootState = { time: { dateTimes: [{ date: mockToday, taskTimes }], + activeTimer: undefined, }, } as RootState; @@ -555,4 +687,45 @@ describe('timeSlice', () => { expect(result).toEqual([]); }); }); + + describe('selectActiveTimer', () => { + it('should return undefined when no timer is active', () => { + const state: RootState = { + time: { + dateTimes: [], + activeTimer: undefined, + }, + } as RootState; + + const result = selectActiveTimer(state); + + expect(result).toBeUndefined(); + }); + + it('should return the active timer when one exists', () => { + const startTimestamp = Date.now(); + const state: RootState = { + time: { + dateTimes: [ + { + date: mockToday, + taskTimes: [{ task: 1, start: startTimestamp }], + }, + ], + activeTimer: { + taskId: 1, + startTime: startTimestamp, + date: mockToday, + }, + }, + } as RootState; + + const result = selectActiveTimer(state); + + expect(result).toBeDefined(); + expect(result?.taskId).toBe(1); + expect(result?.startTime).toBe(startTimestamp); + expect(result?.date).toBe(mockToday); + }); + }); }); diff --git a/src/components/time/TimerButton.test.tsx b/src/components/time/TimerButton.test.tsx new file mode 100644 index 0000000..c39f4cc --- /dev/null +++ b/src/components/time/TimerButton.test.tsx @@ -0,0 +1,195 @@ +import { describe, it, expect } from 'vitest'; +import userEvent from '@testing-library/user-event'; +import { TimerButton } from './TimerButton'; +import { + renderWithProviders, + mockToday, +} from '../../test-utils/test-utils'; +import type { RootState } from '../../app/store'; + +describe('TimerButton', () => { + const createPreloadedState = (override: Partial = {}): Partial => ({ + app: { + version: '1.0', + selectedDate: mockToday, + }, + time: { + dateTimes: [{ date: mockToday, taskTimes: [] }], + activeTimer: undefined, + }, + task: { + nextTaskId: 2, + tasks: [{ id: 1, description: 'Test Task' }], + }, + edit: {}, + ...override, + }); + + it('should render "Start Timer" button when no timer is active', () => { + const { container } = renderWithProviders( + , + { preloadedState: createPreloadedState() } + ); + + const button = container.querySelector('button'); + expect(button).toBeTruthy(); + expect(button?.textContent).toContain('Start Timer'); + }); + + it('should start a timer when "Start Timer" is clicked', async () => { + const user = userEvent.setup(); + const { container, store } = renderWithProviders( + , + { preloadedState: createPreloadedState() } + ); + + const button = container.querySelector('button'); + expect(button).toBeTruthy(); + + await user.click(button!); + + const state = store.getState(); + expect(state.time.activeTimer).toBeDefined(); + expect(state.time.activeTimer?.taskId).toBe(1); + expect(state.time.activeTimer?.date).toBe(mockToday); + }); + + it('should show elapsed time display when timer is active', () => { + const startTimestamp = Date.now(); + const preloadedState = createPreloadedState({ + time: { + dateTimes: [ + { + date: mockToday, + taskTimes: [{ task: 1, start: startTimestamp }], + }, + ], + activeTimer: { + taskId: 1, + startTime: startTimestamp, + date: mockToday, + }, + }, + }); + + const { container } = renderWithProviders( + , + { preloadedState } + ); + + const button = container.querySelector('button'); + expect(button).toBeTruthy(); + // Should show time in format HH:MM:SS + expect(button?.textContent).toMatch(/\d{2}:\d{2}:\d{2}/); + }); + + it('should stop the timer when clicked while active', async () => { + const user = userEvent.setup(); + const startTimestamp = Date.now() - 5000; + const preloadedState = createPreloadedState({ + time: { + dateTimes: [ + { + date: mockToday, + taskTimes: [{ task: 1, start: startTimestamp }], + }, + ], + activeTimer: { + taskId: 1, + startTime: startTimestamp, + date: mockToday, + }, + }, + }); + + const { container, store } = renderWithProviders( + , + { preloadedState } + ); + + const button = container.querySelector('button'); + expect(button).toBeTruthy(); + + await user.click(button!); + + const state = store.getState(); + expect(state.time.activeTimer).toBeUndefined(); + expect(state.time.dateTimes[0].taskTimes[0].end).toBeDefined(); + }); + + it('should disable the button when another task has an active timer', () => { + const startTimestamp = Date.now(); + const preloadedState = createPreloadedState({ + time: { + dateTimes: [ + { + date: mockToday, + taskTimes: [{ task: 2, start: startTimestamp }], + }, + ], + activeTimer: { + taskId: 2, + startTime: startTimestamp, + date: mockToday, + }, + }, + task: { + nextTaskId: 3, + tasks: [ + { id: 1, description: 'Test Task 1' }, + { id: 2, description: 'Test Task 2' }, + ], + }, + }); + + const { container } = renderWithProviders( + , + { preloadedState } + ); + + const button = container.querySelector('button') as HTMLButtonElement; + expect(button).toBeTruthy(); + expect(button.disabled).toBe(true); + }); + + it('should show contained error style when timer is active for this task', () => { + const startTimestamp = Date.now(); + const preloadedState = createPreloadedState({ + time: { + dateTimes: [ + { + date: mockToday, + taskTimes: [{ task: 1, start: startTimestamp }], + }, + ], + activeTimer: { + taskId: 1, + startTime: startTimestamp, + date: mockToday, + }, + }, + }); + + const { container } = renderWithProviders( + , + { preloadedState } + ); + + const button = container.querySelector('button'); + expect(button).toBeTruthy(); + expect(button?.classList.contains('MuiButton-contained')).toBe(true); + expect(button?.classList.contains('MuiButton-containedError')).toBe(true); + }); + + it('should show outlined primary style when no timer is active', () => { + const { container } = renderWithProviders( + , + { preloadedState: createPreloadedState() } + ); + + const button = container.querySelector('button'); + expect(button).toBeTruthy(); + expect(button?.classList.contains('MuiButton-outlined')).toBe(true); + expect(button?.classList.contains('MuiButton-outlinedPrimary')).toBe(true); + }); +}); From 3209d640203e98285be23c675f9ebf88e404b050 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 04:16:01 +0000 Subject: [PATCH 3/3] Fix CI failures: formatting and TypeScript errors Fixed issues that were causing CI failures: - Applied Prettier formatting to TimerButton.test.tsx - Added missing 'type' field to Task objects in tests (required by task types feature) - Fixed TypeScript type assertions in test files - Used 'as unknown as RootState' for partial state mocks in timeSlice.test.ts - Added proper RootState type assertions for store.getState() calls All 236 tests passing and build successful. --- src/app/slices/timeSlice.test.ts | 4 ++-- src/components/time/TimerButton.test.tsx | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/app/slices/timeSlice.test.ts b/src/app/slices/timeSlice.test.ts index 495129d..85a4a40 100644 --- a/src/app/slices/timeSlice.test.ts +++ b/src/app/slices/timeSlice.test.ts @@ -715,12 +715,12 @@ describe('timeSlice', () => { describe('selectActiveTimer', () => { it('should return undefined when no timer is active', () => { - const state: RootState = { + const state = { time: { dateTimes: [], activeTimer: undefined, }, - } as RootState; + } as unknown as RootState; const result = selectActiveTimer(state); diff --git a/src/components/time/TimerButton.test.tsx b/src/components/time/TimerButton.test.tsx index c39f4cc..5cafd5d 100644 --- a/src/components/time/TimerButton.test.tsx +++ b/src/components/time/TimerButton.test.tsx @@ -1,14 +1,13 @@ import { describe, it, expect } from 'vitest'; import userEvent from '@testing-library/user-event'; import { TimerButton } from './TimerButton'; -import { - renderWithProviders, - mockToday, -} from '../../test-utils/test-utils'; +import { renderWithProviders, mockToday } from '../../test-utils/test-utils'; import type { RootState } from '../../app/store'; describe('TimerButton', () => { - const createPreloadedState = (override: Partial = {}): Partial => ({ + const createPreloadedState = ( + override: Partial = {} + ): Partial => ({ app: { version: '1.0', selectedDate: mockToday, @@ -19,7 +18,7 @@ describe('TimerButton', () => { }, task: { nextTaskId: 2, - tasks: [{ id: 1, description: 'Test Task' }], + tasks: [{ id: 1, description: 'Test Task', type: 'task' }], }, edit: {}, ...override, @@ -48,7 +47,7 @@ describe('TimerButton', () => { await user.click(button!); - const state = store.getState(); + const state = store.getState() as RootState; expect(state.time.activeTimer).toBeDefined(); expect(state.time.activeTimer?.taskId).toBe(1); expect(state.time.activeTimer?.date).toBe(mockToday); @@ -112,7 +111,7 @@ describe('TimerButton', () => { await user.click(button!); - const state = store.getState(); + const state = store.getState() as RootState; expect(state.time.activeTimer).toBeUndefined(); expect(state.time.dateTimes[0].taskTimes[0].end).toBeDefined(); }); @@ -136,8 +135,8 @@ describe('TimerButton', () => { task: { nextTaskId: 3, tasks: [ - { id: 1, description: 'Test Task 1' }, - { id: 2, description: 'Test Task 2' }, + { id: 1, description: 'Test Task 1', type: 'task' }, + { id: 2, description: 'Test Task 2', type: 'task' }, ], }, });