Skip to content
This repository was archived by the owner on Nov 10, 2025. It is now read-only.
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
6 changes: 4 additions & 2 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { StopWatch } from './src/StopWatch';

export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StopWatch />
<StatusBar style="auto" />
</View>
);
Expand Down
137 changes: 133 additions & 4 deletions src/StopWatch.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,137 @@
import { View } from 'react-native';
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
import { StopWatchButton } from './StopWatchButton';

export const StopWatch: React.FC = () => {
const [time, setTime] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [lastLapTime, setLastLapTime] = useState<number | null>(null);
const [laps, setLaps] = useState<number[]>([]);
const [startButtonDisabled, setStartButtonDisabled] = useState(false);
const [lapButtonDisabled, setLapButtonDisabled] = useState(false);

const startStopwatch = () => {
setIsRunning(true);
setStartButtonDisabled(true);
setLapButtonDisabled(false);
setLastLapTime(Date.now());
};

const stopStopwatch = () => {
setIsRunning(false);
setStartButtonDisabled(false);
setLapButtonDisabled(true);
setTime(0);
};

const pauseStopwatch = () => {
setIsRunning(false);
setStartButtonDisabled(false);
};

const resetStopwatch = () => {
setTime(0);
setLaps([]);
setIsRunning(false);
setLastLapTime(null);
setStartButtonDisabled(false);
setLapButtonDisabled(false);
};

const recordLap = () => {
if (lastLapTime !== null) {
let lapElapsedTime = time;

if (time >= lastLapTime) {
lapElapsedTime = time - lastLapTime;
} else {
// If the current time is less than the last lap time, assume lap time is the elapsed time from the start
lapElapsedTime = time;
}
setLaps((prevLaps) => [
...prevLaps, // Append the existing laps
lapElapsedTime, // Add the lap time interval to the laps array
]);
} else {
// Handle the first lap separately, assuming the lap time is the elapsed time from the start
setLaps([time]);
}

// Set the current lap time for the next lap
setLastLapTime(time);
};

const formatTime = (milliseconds: number) => {
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);

const formattedMinutes = String(minutes % 60).padStart(2, '0');
const formattedSeconds = String(seconds % 60).padStart(2, '0');
const formattedMilliseconds = String(milliseconds % 1000).padStart(3, '0');

return `${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds.slice(0, 2)}`;
};

useEffect(() => {
let interval: number;

if (isRunning) {
interval = setInterval(() => {
setTime((prevTime) => prevTime + 100);
}, 100);
}

return () => clearInterval(interval);
}, [isRunning]);

export default function StopWatch() {
return (
<View >
<View style={styles.container}>
<View style={styles.stopwatchContainer}>
<Text style={styles.timeText}>{formatTime(time)}</Text>

<View style={styles.buttonContainer}>
<StopWatchButton title="Start" onPress={startStopwatch} disabled={startButtonDisabled}/>
<StopWatchButton title="Pause" onPress={pauseStopwatch} />
<StopWatchButton title="Stop" onPress={stopStopwatch} />
<StopWatchButton title="Reset" onPress={resetStopwatch} />
<StopWatchButton title="Lap" onPress={recordLap} disabled={lapButtonDisabled}/>
</View>
</View>

<FlatList data-test-id='lap-list'
data={laps}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<Text>{`Lap ${index + 1}: ${formatTime(item)}`}</Text>
)}
style={styles.lapList}
/>
</View>
);
}
};

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'flex-start',
marginTop: 50,
},
stopwatchContainer: {
marginTop: 250,
alignItems: 'center',
},
timeText: {
fontSize: 24,
marginBottom: 10,
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
gap: 10
},
lapList: {
maxHeight: 390,
marginTop: 25,
},
});
35 changes: 30 additions & 5 deletions src/StopWatchButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
import { View } from 'react-native';
import React from 'react';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';

export default function StopWatchButton() {
interface StopWatchButtonProps {
title: string;
onPress: () => void;
disabled?: boolean;
}

export const StopWatchButton: React.FC<StopWatchButtonProps> = ({ title, onPress, disabled }) => {
return (
<View >
</View>
<TouchableOpacity style={[styles.button, disabled && styles.disabledButton]} onPress={onPress} disabled={disabled}>
<View>
<Text style={styles.buttonText}>{title}</Text>
</View>
</TouchableOpacity>
);
}
};

const styles = StyleSheet.create({
button: {
backgroundColor: '#007BFF',
padding: 10,
borderRadius: 5,
},
disabledButton: {
backgroundColor: '#CCCCCC', // Customize the disabled button style
},
buttonText: {
color: '#FFFFFF',
fontSize: 16,
},
});
16 changes: 8 additions & 8 deletions tests/Stopwatch.test.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import Stopwatch from '../src/Stopwatch';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { StopWatch } from '../src/StopWatch';

describe('Stopwatch', () => {
test('renders initial state correctly', () => {
const { getByText, queryByTestId } = render(<Stopwatch />);
const { getByText, queryByTestId } = render(<StopWatch />);

expect(getByText('00:00:00')).toBeTruthy();
expect(queryByTestId('lap-list')).toBeNull();
});

test('starts and stops the stopwatch', () => {
const { getByText, queryByText } = render(<Stopwatch />);
const { getByText, queryByText } = render(<StopWatch />);

fireEvent.press(getByText('Start'));
expect(queryByText(/(\d{2}:){2}\d{2}/)).toBeTruthy();
Expand All @@ -21,18 +21,18 @@ describe('Stopwatch', () => {
});

test('pauses and resumes the stopwatch', () => {
const { getByText } = render(<Stopwatch />);
const { getByText } = render(<StopWatch />);

fireEvent.press(getByText('Start'));
fireEvent.press(getByText('Pause'));
const pausedTime = getByText(/(\d{2}:){2}\d{2}/).textContent;

fireEvent.press(getByText('Resume'));
fireEvent.press(getByText('Start'));
expect(getByText(/(\d{2}:){2}\d{2}/).textContent).not.toBe(pausedTime);
});

test('records and displays lap times', () => {
const { getByText, getByTestId } = render(<Stopwatch />);
const { getByText, getByTestId } = render(<StopWatch />);

fireEvent.press(getByText('Start'));
fireEvent.press(getByText('Lap'));
Expand All @@ -43,7 +43,7 @@ describe('Stopwatch', () => {
});

test('resets the stopwatch', () => {
const { getByText, queryByTestId } = render(<Stopwatch />);
const { getByText, queryByTestId } = render(<StopWatch />);

fireEvent.press(getByText('Start'));
fireEvent.press(getByText('Lap'));
Expand Down