)
}
\ No newline at end of file
diff --git a/src/StopWatch.test.tsx b/src/StopWatch.test.tsx
index 643343c3..6196f90d 100644
--- a/src/StopWatch.test.tsx
+++ b/src/StopWatch.test.tsx
@@ -1,68 +1,87 @@
import React from 'react';
-import { render, fireEvent, screen } from '@testing-library/react';
-import StopWatch, { formatTime } from './StopWatch';
+import { render, fireEvent } from '@testing-library/react'; // Import render and fireEvent from testing-library
+import StopWatchButton from './StopWatchButton'; // Import the StopWatchButton component
+import StopWatch from './StopWatch'; // Import the StopWatch component
-// Test the formatTime function
-describe('formatTime', () => {
- test('formats time less than an hour correctly', () => {
- expect(formatTime(5900)).toBe('00:59:00');
- expect(formatTime(6000)).toBe('01:00:00');
- expect(formatTime(359900)).toBe('59:59:00');
+describe('StopWatch component', () => {
+ // Test to ensure that the StopWatch component renders without crashing
+ test('renders without crashing', () => {
+ render();
});
- test('formats time greater than an hour correctly', () => {
- expect(formatTime(360000)).toBe('01:00:00:00');
- expect(formatTime(366100)).toBe('01:01:01:00');
+ // Test to check if clicking the start button triggers the onStart function
+ test('clicking start button triggers onStart function', () => {
+ // Create a mock function for onStart
+ const onStart = jest.fn();
+ // Render the StopWatchButton component with the mock onStart function
+ const { getByText } = render();
+
+ // Simulate a click on the start button
+ fireEvent.click(getByText('Start'));
+ // Assert that onStart function has been called
+ expect(onStart).toHaveBeenCalled();
});
-});
-test('renders correctly', () => {
- const { getByText } = render();
- const stopwatchElement = getByText('StopWatch');
- expect(stopwatchElement).not.toBeNull();
-});
+ // Test to check if clicking the stop button triggers the onStop function
+ test('clicking stop button triggers onStop function', () => {
+ // Create a mock function for onStop
+ const onStop = jest.fn();
+ // Render the StopWatchButton component with the mock onStop function
+ const { getByText } = render();
-// Use fake timers for timer-related tests
-jest.useFakeTimers();
+ // Simulate a click on the stop button
+ fireEvent.click(getByText('Stop'));
+ // Assert that onStop function has been called
+ expect(onStop).toHaveBeenCalled();
+ });
-test('starts timer when start button is clicked', () => {
- const setIntervalSpy = jest.spyOn(global, 'setInterval');
- render();
- const startButton = screen.getByRole('button', { name: /start/i });
- fireEvent.click(startButton);
- jest.advanceTimersByTime(1000);
- expect(setIntervalSpy).toHaveBeenCalledTimes(1);
- expect(setIntervalSpy).toHaveBeenLastCalledWith(expect.any(Function), 10);
-});
+ // Test to check if clicking the lap button triggers the onLap function
+ test('clicking lap button triggers onLap function', () => {
+ // Create a mock function for onLap
+ const onLap = jest.fn();
+ // Render the StopWatchButton component with the mock onLap function
+ const { getByText } = render();
-test('stops timer when stop button is clicked', () => {
- const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
- const setIntervalSpy = jest.spyOn(global, 'setInterval');
- setIntervalSpy.mockImplementation(() => 123 as unknown as NodeJS.Timeout);
- render();
- const startButton = screen.getByRole('button', { name: /start/i });
- fireEvent.click(startButton);
- jest.advanceTimersByTime(1000);
- const stopButton = screen.getByRole('button', { name: /stop/i });
- fireEvent.click(stopButton);
- expect(clearIntervalSpy).toHaveBeenCalledWith(123);
-});
+ // Simulate a click on the lap button
+ fireEvent.click(getByText('Lap'));
+ // Assert that onLap function has been called
+ expect(onLap).toHaveBeenCalled();
+ });
-beforeEach(() => {
- jest.useRealTimers();
-});
+ // Test to check if clicking the reset button triggers the onReset function
+ test('clicking reset button triggers onReset function', () => {
+ // Create a mock function for onReset
+ const onReset = jest.fn();
+ // Render the StopWatchButton component with the mock onReset function
+ const { getByText } = render();
-afterEach(() => {
- jest.useFakeTimers();
- jest.clearAllMocks();
+ // Simulate a click on the reset button
+ fireEvent.click(getByText('Reset'));
+ // Assert that onReset function has been called
+ expect(onReset).toHaveBeenCalled();
+ });
});
-
-test('resets timer when reset button is clicked', () => {
- const { getByRole, getByText } = render();
- const startButton = getByRole('button', { name: /start/i });
- fireEvent.click(startButton);
- jest.advanceTimersByTime(1000);
- const resetButton = getByRole('button', { name: /reset/i });
- fireEvent.click(resetButton);
- expect(getByText('00:00:00')).not.toBeNull();
-});
\ No newline at end of file
diff --git a/src/StopWatch.tsx b/src/StopWatch.tsx
index 2e06473c..a6f37ce3 100644
--- a/src/StopWatch.tsx
+++ b/src/StopWatch.tsx
@@ -1,77 +1,97 @@
-import React, { useState, useEffect, useCallback } from 'react'
-import StopWatchButton from './StopWatchButton'
+import React, { useState, useRef, useEffect } from "react";
+import StopWatchButton from "./StopWatchButton";
+
+export default function StopWatch() {
+ // Define state variables using the useState hook
+ const [startTime, setStartTime] = useState(null); // Start time of the stopwatch
+ const [elaspedTime, setElaspedTime] = useState(null); // Elapsed time since starting the stopwatch
+ const [isRunning, setIsRunning] = useState(null); // Flag to indicate if the stopwatch is running or not
+ const [lapHistory, setLapHistory] = useState([]); // Array to store lap times
+ const [lapTime, setLapTime] = useState(null); // Last lap time recorded
+ const intervalRef = useRef(null); // Reference to the interval used for updating the stopwatch
+
+ // Calculate the elapsed time in seconds
+ let secondsPassed: number = 0;
+
+ // Function to format time in hours:minutes:seconds.milliseconds format
+ const formatTime = (time: number): string => {
+ const hours = Math.floor(time / 3600);
+ const minutes = Math.floor((time % 3600) / 60).toString().padStart(2, '0');
+ const seconds = Math.floor(time % 60).toString().padStart(2, '0');
+ const milliseconds = Math.floor((time % 1) * 1000).toString().padStart(3, '0');
-// Function to format the time. This is necessary since both the time and lap times need to be formatted
-export function formatTime(time: number): string {
- // Format the time in mm:ss:ms. Display hours only if reached
- const hours = Math.floor(time / 360000);
- const minutes = Math.floor((time % 360000) / 6000);
- const seconds = Math.floor((time % 6000) / 100);
- const milliseconds = time % 100;
- // Format the minutes, seconds, and milliseconds to be two digits
- const formattedMinutes = minutes.toString().padStart(2, '0');
- const formattedSeconds = seconds.toString().padStart(2, '0');
- const formattedMilliseconds = milliseconds.toString().padStart(2, '0');
- // If stopwatch reaches at least an hour, display the hours
if (hours > 0) {
- const formattedHours = hours.toString().padStart(2, '0');
- return `${formattedHours}:${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds}`;
+ return `${hours}:${minutes}:${seconds}.${milliseconds}`;
+ } else {
+ return `${minutes}:${seconds}.${milliseconds}`;
}
- // Combine the values into a string
- const formattedTime = `${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds}`;
- return formattedTime;
-}
+ };
-export default function StopWatch() {
- // State to track the time, whether the timer is on/off, and the lap times
- const [time, setTime] = useState(0);
- const [timerOn, setTimerOn] = useState(false);
- const [lapTimes, setLapTimes] = useState([]);
+ // Function to handle starting the stopwatch
+ const handleStart = () => {
+ if (!isRunning) {
+ const currentTime = Date.now();
+ // Set start time and elapsed time
+ setStartTime(currentTime - elaspedTime);
+ setElaspedTime(currentTime);
+ setLapHistory([...lapHistory]);
+ setLapTime(currentTime);
+ // Start the interval for updating the elapsed time
+ clearInterval(intervalRef.current);
+ intervalRef.current = setInterval(() => {
+ setElaspedTime(Date.now() - startTime);
+ }, 50);
+ setIsRunning(true); // Set running flag to true
+ }
+ };
- // Stops the timer, resets the time, and clears the lap times. useCallback is used to prevent unnecessary re-renders
- const handleReset = useCallback(() => {
- setTimerOn(false);
- setTime(0);
- setLapTimes([]);
- }, []);
+ // Function to handle stopping the stopwatch
+ const handleStop = () => {
+ clearInterval(intervalRef.current); // Clear the interval
+ setIsRunning(false); // Set running flag to false
+ };
- // Every time timerOn changes, we start or stop the timer
- // useEffect is necessary since setInterval changes the state and we don't want to create an infinite loop
- useEffect(() => {
- let interval: ReturnType | null = null;
+ // Calculate the elapsed time in seconds if both start and elapsed times are set
+ if (startTime != null && elaspedTime != null) {
+ secondsPassed = (elaspedTime - startTime) / 1000;
+ }
- if (timerOn) {
- interval = setInterval(() => setTime(time => time + 1), 10)
- }
+ // Function to handle recording a lap time
+ const handleLap = () => {
+ if (isRunning) {
+ const lapElapsedTime: number = Date.now() - lapTime;
+ setLapHistory([...lapHistory, lapElapsedTime]); // Add lap time to lap history
+ setLapTime(Date.now()); // Set the current time as the last lap time
+ }
+ };
- return () => {clearInterval(interval)} // Clears the interval when the component unmounts or timerOn changes
- }, [timerOn])
+ // Function to handle resetting the stopwatch
+ const handleReset = () => {
+ clearInterval(intervalRef.current); // Clear the interval
+ setElaspedTime(0); // Reset elapsed time
+ setStartTime(null); // Reset start time
+ setIsRunning(false); // Set running flag to false
+ setLapHistory([]); // Clear lap history
+ setLapTime(null); // Reset last lap time
+ };
- return(
-