Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,509 changes: 944 additions & 565 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"react-test-renderer": "^18.2.0",
"style-loader": "^3.3.2",
"ts-jest": "^29.1.1",
Expand All @@ -46,6 +47,7 @@
"jest": {
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest"
}
},
"testEnvironment": "jsdom"
}
}
8 changes: 6 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React from 'react'
import React, { useState } from 'react'
import StopWatch from './StopWatch'
import './StopWatch.css';

export default function App() {
return(
<div></div>
<div>
<StopWatch />
</div>
)
}
68 changes: 68 additions & 0 deletions src/StopWatch.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* StopWatch.css */
/* StopWatch.css */

body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
font-family: 'Arial', sans-serif;
background-color: #f0f0f0;
}

.stopwatch-container {
text-align: center;
padding: 20px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.stopwatch-display {
font-size: 3em;
margin-bottom: 20px;
color: #333;
}

.button-container {
display: flex;
justify-content: space-around;
margin-bottom: 20px;
}

.stopwatch-button {
background-color: #4caf50;
color: #fff;
padding: 10px 20px;
font-size: 1.2em;
border: none;
border-radius: 5px;
cursor: pointer;
outline: none;
}

.stopwatch-button:disabled {
background-color: #ddd;
color: #666;
cursor: not-allowed;
}

.lap-times {
margin-top: 20px;
}

.lap-times ul {
list-style-type: none;
padding: 0;
}

.lap-times li {
font-size: 1.4em;
background-color: #f8f8f8;
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

104 changes: 104 additions & 0 deletions src/StopWatch.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { render, fireEvent, screen, act } from '@testing-library/react';
import StopWatch, { formattedTime } from './StopWatch';

// Test for the formattedTime function
describe('formatTime', () => {
test('formats time correctly', () => {
expect(formattedTime(0)).toBe('00:00:00');
expect(formattedTime(6000)).toBe('01:00:00');
expect(formattedTime(1234)).toBe('00:12:34');
});
});

//Tests for the Stopwatch component
describe('StopWatch component', () => {
// Test for rendering the StopWatch component
test('renders correctly', () => {
const { getByText } = render(<StopWatch />);
const stopwatchElement = getByText('StopWatch');
expect(stopwatchElement).not.toBeNull();
});
// Test for the Start button functionality
test('Start button starts the timer', async () => {
render(<StopWatch />);

const startButton = screen.getByText('Start');
fireEvent.click(startButton);

await act(async () => {
await new Promise(resolve => setTimeout(resolve, 20));
});

const displayedTime = screen.getByText(/\d+:\d+:\d+/).textContent;

expect(displayedTime).not.toBe('00:00:00');
});
// Test for the Stop button functionality
test('Stop button pauses the timer without resetting it and displays the correct time', async () => {
render(<StopWatch />);

const startButton = screen.getByText('Start');
fireEvent.click(startButton);

await act(async () => {
await new Promise(resolve => setTimeout(resolve, 20));
});

const initialDisplayedTime = screen.getByText(/\d+:\d+:\d+/).textContent;
console.log(initialDisplayedTime);
const stopButton = screen.getByText('Stop');
fireEvent.click(stopButton);

const stoppedDisplayedTime = screen.getByText(/\d+:\d+:\d+/).textContent;
console.log(stoppedDisplayedTime);
expect(stoppedDisplayedTime).toBe(initialDisplayedTime);
});
// Test for the Reset button functionality
test('Reset button', async () => {
render(<StopWatch />);

const startButton = screen.getByText('Start');
fireEvent.click(startButton);

await act(async () => {
await new Promise(resolve => setTimeout(resolve, 20));
});

const resetButton = screen.getByText('Reset');
fireEvent.click(resetButton);

const displayedTime = screen.getByText(/\d+:\d+:\d+/).textContent;

expect(displayedTime).toBe('00:00:00');
});
// Test for the Lap button functionality
test('Lap button records lap time when the timer is running', async () => {
render(<StopWatch />);

const startButton = screen.getByText('Start');
fireEvent.click(startButton);

await act(async () => {
await new Promise(resolve => setTimeout(resolve, 20));
});

const initialDisplayedTime = screen.getByText(/\d+:\d+:\d+/).textContent;
console.log(initialDisplayedTime);

const lapButton = screen.getByText('Lap');
fireEvent.click(lapButton);


const lapTimeElements = screen.getAllByText(/\d+:\d+:\d+/);

const lapTimeDisplayed = lapTimeElements[0].textContent;
console.log(lapTimeDisplayed);

expect(lapTimeDisplayed).toBe(initialDisplayedTime);
});

});



74 changes: 69 additions & 5 deletions src/StopWatch.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,71 @@
import React from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import StopWatchButton from './StopWatchButton';

// Function to format the time in minutes, seconds, and milliseconds
export function formattedTime(time: number): string {
// Calculate total hours, remaining minutes, seconds, and milliseconds
const totalHours = Math.floor(time / 360000);
const remainingMinutes = Math.floor((time % 360000) / 6000);
const remainingSeconds = Math.floor((time % 6000) / 100);
const remainingMilliseconds = time % 100;

// Format individual components to ensure they have two digits
const formattedMinutes = remainingMinutes.toString().padStart(2, '0');
const formattedSeconds = remainingSeconds.toString().padStart(2, '0');
const formattedMilliseconds = remainingMilliseconds.toString().padStart(2, '0');

// Combine formatted components into a string representing the time
return `${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds}`;
}

// StopWatch component
export default function StopWatch() {
return(
<div></div>
)
}
// State to track time, whether the timer is running, and lap times
const [time, setTime] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [laps, setLaps] = useState<number[]>([]);

// Callback function to handle resetting the stopwatch
const handleReset = useCallback(() => {
setIsRunning(false);
setTime(0);
setLaps([]);
}, []);

// Effect to update time every second when the timer is running
useEffect(() => {
let intervalId: NodeJS.Timeout;
if (isRunning) {
intervalId = setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, 10);
}
// Clear the interval when the component unmounts or when the timer is stopped
return () => clearInterval(intervalId);
}, [isRunning]);

return (
<div className="stopwatch-container">
<h1>StopWatch</h1>
<div className="stopwatch-display">
<p>{formattedTime(time)}</p>
</div>
<div className="button-container">
<StopWatchButton type="start" onClick={() => setIsRunning(true)} disabled={isRunning} />
<StopWatchButton type="stop" onClick={() => setIsRunning(false)} disabled={!isRunning} />
<StopWatchButton type="lap" onClick={() => setLaps([...laps, time])} disabled={!isRunning} />
<StopWatchButton type="reset" onClick={handleReset} />
</div>
{laps.length > 0 && (
<div className="lap-times">
<h2>Lap Times</h2>
<ul>
{laps.map((lap, index) => (
<li key={index}>{formattedTime(lap)}</li>
))}
</ul>
</div>
)}
</div>
);
}
34 changes: 30 additions & 4 deletions src/StopWatchButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
import React from 'react'

export default function StopWatchButton() {
return(
<div></div>
)
// Props interface for the StopWatchButton component
interface StopWatchButtonProps {
type: string;
onClick: () => void;
disabled?: boolean;
}

//StopWatchButton component
export default function StopWatchButton({ type, onClick, disabled = false }: StopWatchButtonProps) {
// Function to determine the text content of the button based on its type
const getButtonText = (): string => {
switch (type) {
case 'start':
return 'Start';
case 'stop':
return 'Stop';
case 'lap':
return 'Lap';
case 'reset':
return 'Reset';
default:
return '';
}
};

return (
<button onClick={onClick} disabled={disabled}>
{getButtonText()}
</button>
);
}