From d478a2cb9b0ca49c46ad0efb0386bb3e5b27e574 Mon Sep 17 00:00:00 2001 From: Syed Basim Ali Date: Tue, 30 Jan 2024 00:17:23 -0330 Subject: [PATCH 1/5] Create StopWatchTimer component and formatTime utility function --- src/StopWatchTimer.tsx | 15 +++++++++++++++ src/utils.ts | 13 +++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 src/StopWatchTimer.tsx create mode 100644 src/utils.ts diff --git a/src/StopWatchTimer.tsx b/src/StopWatchTimer.tsx new file mode 100644 index 00000000..01f28c4f --- /dev/null +++ b/src/StopWatchTimer.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { formatTime } from './utils'; + +interface StopwatchTimerProps { + time: number; + } + +const StopwatchTimer: React.FC = (props) => { + + return ( +
{formatTime(props.time)}
+ ); +}; + +export default StopwatchTimer; \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..78956825 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,13 @@ +export const formatTime = (milliseconds: number): string => { + const hours = Math.floor(milliseconds / 3600000); + const minutes = Math.floor((milliseconds % 3600000) / 60000); + const seconds = Math.floor((milliseconds % 60000) / 1000); + const remainingMilliseconds = Math.floor((milliseconds % 1000) / 10); + + return `${padZero(hours)}:${padZero(minutes)}:${padZero(seconds)}:${padZero(remainingMilliseconds)}`; +}; + +export const padZero = (num: number, length: number = 2): string => { + const str = num.toString(); + return str.length >= length ? str : '0'.repeat(length - str.length) + str; +}; From 8fd93936dcf9a95ccd6094fb972bb4874a137766 Mon Sep 17 00:00:00 2001 From: Syed Basim Ali Date: Tue, 30 Jan 2024 00:52:14 -0330 Subject: [PATCH 2/5] Add dependencies for unit testing --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index cf0d970a..feb710c4 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@babel/preset-env": "^7.23.8", "@babel/preset-react": "^7.23.3", "@babel/preset-typescript": "^7.23.3", + "@testing-library/jest-dom": "^6.4.0", "@testing-library/react": "^14.1.2", "@types/jest": "^29.5.11", "@types/node": "^16.18.30", @@ -32,6 +33,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", @@ -46,6 +48,7 @@ "jest": { "transform": { "^.+\\.(js|jsx|ts|tsx)$": "babel-jest" - } + }, + "testEnvironment": "jsdom" } } From 2f138d69d4026db9f797dee0378731041513e77c Mon Sep 17 00:00:00 2001 From: Syed Basim Ali Date: Tue, 30 Jan 2024 00:58:16 -0330 Subject: [PATCH 3/5] Create StopWatchButton with unit tests --- src/StopWatchButton.tsx | 22 +++++++++++----- src/tests/StopwatchButton.test.tsx | 41 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/tests/StopwatchButton.test.tsx diff --git a/src/StopWatchButton.tsx b/src/StopWatchButton.tsx index dbd7c174..78ef0484 100644 --- a/src/StopWatchButton.tsx +++ b/src/StopWatchButton.tsx @@ -1,7 +1,17 @@ -import React from 'react' +import React from 'react'; -export default function StopWatchButton() { - return( -
- ) -} \ No newline at end of file +interface StopwatchButtonProps { + label: string; + className: string; + onClick: () => void; +} + +const StopwatchButton: React.FC = (props: StopwatchButtonProps) => { + return ( + + ); +}; + +export default StopwatchButton; \ No newline at end of file diff --git a/src/tests/StopwatchButton.test.tsx b/src/tests/StopwatchButton.test.tsx new file mode 100644 index 00000000..411f0863 --- /dev/null +++ b/src/tests/StopwatchButton.test.tsx @@ -0,0 +1,41 @@ +import '@testing-library/jest-dom'; +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import StopwatchButton from '../StopWatchButton'; + +describe('StopwatchButton Component', () => { + const mockOnClick = jest.fn(); + + const renderComponent = (label: string, className: string) => { + render( + + ); + }; + + test('renders button with provided label and className', () => { + const label = 'Start'; + const className = 'start-button'; + + renderComponent(label, className); + + const button = screen.getByText(label); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass(className); + }); + + test('calls onClick handler when the button is clicked', () => { + const label = 'Start'; + const className = 'start-button'; + + renderComponent(label, className); + + const button = screen.getByText(label); + fireEvent.click(button); + + expect(mockOnClick).toHaveBeenCalledTimes(1); + }); +}); From d992209a15ce062f898473a62dc77df794aa8d21 Mon Sep 17 00:00:00 2001 From: Syed Basim Ali Date: Tue, 30 Jan 2024 01:11:31 -0330 Subject: [PATCH 4/5] Add timer and buttons functionality to StopWatch --- public/index.html | 23 +++++++++---- src/App.tsx | 18 ++++++---- src/StopWatch.tsx | 71 ++++++++++++++++++++++++++++++++++++---- src/assets/App.css | 31 ++++++++++++++++++ src/assets/StopWatch.css | 13 ++++++++ src/assets/common.css | 71 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 208 insertions(+), 19 deletions(-) create mode 100644 src/assets/App.css create mode 100644 src/assets/StopWatch.css create mode 100644 src/assets/common.css diff --git a/public/index.html b/public/index.html index 5953d96e..f90999df 100755 --- a/public/index.html +++ b/public/index.html @@ -1,9 +1,18 @@ - - - Template Site - - -
- + + + + + + StopWatch App + + + + + + +
+ + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 95351af9..58a8fb3a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,13 @@ -import React from 'react' +import React from 'react'; +import Stopwatch from './StopWatch'; +import './assets/App.css'; -export default function App() { - return( -
- ) -} \ No newline at end of file +const App: React.FC = () => { + return ( +
+ +
+ ); +}; + +export default App; \ No newline at end of file diff --git a/src/StopWatch.tsx b/src/StopWatch.tsx index d58edc78..7508f619 100644 --- a/src/StopWatch.tsx +++ b/src/StopWatch.tsx @@ -1,7 +1,66 @@ -import React from 'react' +import React, { useState, useEffect } from 'react'; +import './assets/StopWatch.css'; +import StopwatchTimer from './StopWatchTimer'; +import StopwatchButton from './StopWatchButton'; -export default function StopWatch() { - return( -
- ) -} \ No newline at end of file +interface StopwatchProps { } + +const Stopwatch: React.FC = () => { + const [time, setTime] = useState(0); + const [isRunning, setIsRunning] = useState(false); + + useEffect(() => { + let intervalId: NodeJS.Timeout; + + if (isRunning) { + intervalId = setInterval(() => { + setTime((prevTime) => prevTime + 10); + }, 10); + } + + return () => clearInterval(intervalId); + }, [isRunning]); + + const startStopwatch = () => { + setIsRunning(true); + }; + + const stopStopwatch = () => { + setIsRunning(false); + }; + + const resetStopwatch = () => { + setTime(0); + setIsRunning(false); + }; + + return ( +
+ +
+ {isRunning ? ( + + ) : ( + <> + + + + )} +
+
+ ); +}; + +export default Stopwatch; diff --git a/src/assets/App.css b/src/assets/App.css new file mode 100644 index 00000000..eb4f6ab8 --- /dev/null +++ b/src/assets/App.css @@ -0,0 +1,31 @@ +@import 'common.css'; + +body { + background-color: #eeddd3; + margin: 0; + padding: 0; + font-family: 'Helvetica Neue', sans-serif; + color: #dcdcdc; +} + +.app-container { + display: flex; + flex-direction: column; + align-items: center; + min-height: 100vh; + background-color: #eeddd3; + padding: 20px; + box-sizing: border-box; +} + +/* Responsive Layout */ +@media screen and (min-width: 768px) { + .app-container { + max-width: 800px; + margin: 0 auto; + } +} + +.stopwatch-container { + text-align: center; +} \ No newline at end of file diff --git a/src/assets/StopWatch.css b/src/assets/StopWatch.css new file mode 100644 index 00000000..47d267d2 --- /dev/null +++ b/src/assets/StopWatch.css @@ -0,0 +1,13 @@ +/* Stopwatch.css */ + +@import 'common.css'; + +.stopwatch-container { + max-width: 400px; + margin: 0 auto; + text-align: center; + background-color: #fff1e6; + padding: 30px; + border-radius: 15px; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); +} \ No newline at end of file diff --git a/src/assets/common.css b/src/assets/common.css new file mode 100644 index 00000000..a0ca2abd --- /dev/null +++ b/src/assets/common.css @@ -0,0 +1,71 @@ +.timer { + font-size: 3em; + margin: 20px 0; + color: #3b3b3b; +} + +.button-container { + display: flex; + flex-wrap: wrap; + justify-content: space-around; + margin-bottom: 20px; + position: relative; + /* Position relative to make it a reference point for pseudo-element */ +} + +/* Button Styles */ +.start-button, +.stop-button, +.reset-button, +.lap-button { + width: 100px; + height: 100px; + padding: 15px; + font-size: 1.2em; + cursor: pointer; + border: none; + border-radius: 50%; + transition: background-color 0.5s ease; + margin: 40px; +} + +.button-container::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 1px; + /* Adjust the height for the thickness of the line */ + background-color: rgba(59, 59, 59, 0.3); +} + +.start-button { + background-color: #6A9C89; + color: white; +} + +.stop-button { + background-color: #9D2503; + color: white; +} + +.reset-button { + background-color: #CD5C08; + color: white; +} + +.lap-button { + background-color: #3b3b3b; + color: white; +} + +.lap-list { + text-align: center; + margin-top: 20px; +} + +.lap-item { + margin: 20px; + color: #808080; +} \ No newline at end of file From c37f4205161c47d50390612976c44f4aea1730b2 Mon Sep 17 00:00:00 2001 From: Syed Basim Ali Date: Tue, 30 Jan 2024 01:34:11 -0330 Subject: [PATCH 5/5] Add StopWatchLaps to StopWatch --- src/StopWatch.tsx | 16 ++++++++++++++++ src/StopWatchLaps.tsx | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/StopWatchLaps.tsx diff --git a/src/StopWatch.tsx b/src/StopWatch.tsx index 7508f619..4c2024ff 100644 --- a/src/StopWatch.tsx +++ b/src/StopWatch.tsx @@ -1,13 +1,16 @@ import React, { useState, useEffect } from 'react'; import './assets/StopWatch.css'; import StopwatchTimer from './StopWatchTimer'; +import { formatTime } from './utils'; import StopwatchButton from './StopWatchButton'; +import StopwatchLaps from './StopWatchLaps'; interface StopwatchProps { } const Stopwatch: React.FC = () => { const [time, setTime] = useState(0); const [isRunning, setIsRunning] = useState(false); + const [laps, setLaps] = useState([]); useEffect(() => { let intervalId: NodeJS.Timeout; @@ -32,6 +35,11 @@ const Stopwatch: React.FC = () => { const resetStopwatch = () => { setTime(0); setIsRunning(false); + setLaps([]); + }; + + const recordLap = () => { + setLaps((prevLaps) => [...prevLaps, time]); }; return ( @@ -39,11 +47,18 @@ const Stopwatch: React.FC = () => {
{isRunning ? ( + <> + + ) : ( <> = () => { )}
+ ); }; diff --git a/src/StopWatchLaps.tsx b/src/StopWatchLaps.tsx new file mode 100644 index 00000000..3fed181d --- /dev/null +++ b/src/StopWatchLaps.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { formatTime } from './utils'; + +interface StopwatchLapsProps { + laps: number[]; +} + +const StopwatchLaps: React.FC = (props: StopwatchLapsProps) => { + return ( +
+ {props.laps.map((lapTime, index) => ( +
{`Lap ${index + 1} - ${formatTime(lapTime)}`}
+ ))} +
+ ); +}; + +export default StopwatchLaps; \ No newline at end of file