Skip to content

Commit 073bed2

Browse files
Add last-lap exercise
1 parent 9210b05 commit 073bed2

File tree

2 files changed

+258
-17
lines changed

2 files changed

+258
-17
lines changed

exercises/practice/split-second-stopwatch/SplitSecondStopwatch.cs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,67 @@ public enum StopwatchState
22
{
33
Ready,
44
Running,
5-
Paused,
6-
Stopped
5+
Paused
76
}
87

98
public class SplitSecondStopwatch(TimeProvider time)
109
{
10+
private readonly List<TimeSpan> _previousLapSegments = new();
11+
private DateTimeOffset? _currentSegmentStart;
12+
1113
public StopwatchState State { get; private set; }
1214

15+
public List<TimeSpan> PreviousLaps { get; } = new();
16+
17+
public TimeSpan CurrentLap => PreviousLapSegments + CurrentSegment;
18+
19+
private TimeSpan PreviousLapSegments => _previousLapSegments.Aggregate(TimeSpan.Zero, (total, segment) => total + segment);
20+
21+
private TimeSpan CurrentSegment => _currentSegmentStart is {} start ? time.GetUtcNow() - start : TimeSpan.Zero;
22+
1323
public void Start()
1424
{
15-
throw new NotImplementedException();
25+
if (State == StopwatchState.Paused)
26+
_currentSegmentStart = null;
27+
else if (State == StopwatchState.Running)
28+
throw new InvalidOperationException();
29+
30+
_currentSegmentStart ??= time.GetUtcNow();
31+
32+
State = StopwatchState.Running;
1633
}
1734

1835
public void Stop()
1936
{
20-
throw new NotImplementedException();
37+
if (State == StopwatchState.Ready)
38+
return;
39+
40+
PreviousLaps.Add(CurrentLap);
41+
_currentSegmentStart = null;
42+
State = StopwatchState.Ready;
2143
}
2244

2345
public void Split()
2446
{
25-
throw new NotImplementedException();
47+
AddSegment();
2648
}
2749

2850
public void Pause()
2951
{
30-
throw new NotImplementedException();
52+
State = StopwatchState.Paused;
53+
AddSegment();
54+
}
55+
56+
public void Reset()
57+
{
58+
Stop();
59+
_previousLapSegments.Clear();
60+
Start();
61+
}
62+
63+
private void AddSegment()
64+
{
65+
_previousLapSegments.Add(CurrentSegment);
66+
_currentSegmentStart = null;
3167
}
3268
}

exercises/practice/split-second-stopwatch/SplitSecondStopwatchTests.cs

Lines changed: 216 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,258 @@
33
public class SplitSecondStopwatchTests
44
{
55
[Fact]
6-
public void InitalStateIsReady()
6+
public void StateIsInitiallyReady()
77
{
88
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
99
Assert.Equal(StopwatchState.Ready, stopwatch.State);
1010
}
11-
11+
12+
[Fact]
13+
public void PreviousLapsIsInitiallyEmpty()
14+
{
15+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
16+
Assert.Empty(stopwatch.PreviousLaps);
17+
}
18+
19+
[Fact]
20+
public void CurrentLapIsInitiallyZero()
21+
{
22+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
23+
Assert.Equal(TimeSpan.Zero, stopwatch.CurrentLap);
24+
}
25+
1226
[Fact]
13-
public void StartChangesStateToRunning()
27+
public void StartFromReadyStateChangesStateToRunning()
1428
{
1529
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
1630
stopwatch.Start();
1731
Assert.Equal(StopwatchState.Running, stopwatch.State);
1832
}
33+
34+
[Fact]
35+
public void StartFromReadyStateDoesNotChangePreviousLaps()
36+
{
37+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
38+
stopwatch.Start();
39+
Assert.Empty(stopwatch.PreviousLaps);
40+
}
41+
42+
[Fact]
43+
public void StartFromReadyStateStartsTimeTrackingInCurrentLap()
44+
{
45+
var timeProvider = new FakeTimeProvider();
46+
var stopwatch = new SplitSecondStopwatch(timeProvider);
47+
stopwatch.Start();
48+
49+
var elapsed = TimeSpan.FromSeconds(55.5);
50+
timeProvider.Advance(elapsed);
51+
52+
Assert.Equal(elapsed, stopwatch.CurrentLap);
53+
}
54+
55+
[Fact]
56+
public void StopFromRunningStateChangesStateToReady()
57+
{
58+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
59+
stopwatch.Start();
60+
61+
stopwatch.Stop();
62+
63+
Assert.Equal(StopwatchState.Ready, stopwatch.State);
64+
}
65+
66+
[Fact]
67+
public void StopFromRunningStateAddsCurrentLapToPreviousLaps()
68+
{
69+
var timeProvider = new FakeTimeProvider();
70+
var stopwatch = new SplitSecondStopwatch(timeProvider);
71+
stopwatch.Start();
72+
73+
var elapsed = TimeSpan.FromSeconds(33.6);
74+
timeProvider.Advance(elapsed);
75+
76+
stopwatch.Stop();
77+
78+
var previousLap = Assert.Single(stopwatch.PreviousLaps);
79+
Assert.Equal(elapsed, previousLap);
80+
}
1981

2082
[Fact]
21-
public void PauseChangesStateToPaused()
83+
public void StopFromRunningStateResetsCurrentLapToZero()
84+
{
85+
var timeProvider = new FakeTimeProvider();
86+
var stopwatch = new SplitSecondStopwatch(timeProvider);
87+
stopwatch.Start();
88+
89+
stopwatch.Stop();
90+
91+
Assert.Equal(TimeSpan.Zero, stopwatch.CurrentLap);
92+
}
93+
94+
[Fact]
95+
public void PauseFromRunningStateChangesStateToPaused()
2296
{
2397
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
2498
stopwatch.Start();
99+
25100
stopwatch.Pause();
101+
26102
Assert.Equal(StopwatchState.Paused, stopwatch.State);
27103
}
104+
105+
[Fact]
106+
public void PauseFromRunningStateDoesNotChangePreviousLaps()
107+
{
108+
var timeProvider = new FakeTimeProvider();
109+
var stopwatch = new SplitSecondStopwatch(timeProvider);
110+
stopwatch.Start();
111+
112+
var elapsed = TimeSpan.FromSeconds(33.6);
113+
timeProvider.Advance(elapsed);
114+
115+
stopwatch.Pause();
116+
117+
Assert.Empty(stopwatch.PreviousLaps);
118+
}
28119

29120
[Fact]
30-
public void SplitKeepsStateAsRunning()
121+
public void PauseFromRunningStateFreezesCurrentLap()
122+
{
123+
var timeProvider = new FakeTimeProvider();
124+
var stopwatch = new SplitSecondStopwatch(timeProvider);
125+
stopwatch.Start();
126+
127+
var elapsed = TimeSpan.FromSeconds(33.6);
128+
timeProvider.Advance(elapsed);
129+
130+
stopwatch.Pause();
131+
132+
timeProvider.Advance(TimeSpan.FromSeconds(17.7));
133+
134+
Assert.Equal(elapsed, stopwatch.CurrentLap);
135+
}
136+
137+
138+
139+
[Fact]
140+
public void StartFromPausedStateChangesStateToRunning()
31141
{
32142
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
33143
stopwatch.Start();
34-
stopwatch.Split();
144+
stopwatch.Pause();
145+
146+
stopwatch.Start();
147+
35148
Assert.Equal(StopwatchState.Running, stopwatch.State);
36149
}
150+
151+
[Fact]
152+
public void StartFromPausedStateDoesNotChangePreviousLaps()
153+
{
154+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
155+
stopwatch.Start();
156+
stopwatch.Pause();
157+
158+
stopwatch.Start();
159+
160+
Assert.Empty(stopwatch.PreviousLaps);
161+
}
162+
163+
[Fact]
164+
public void StartFromPausedStateResumesTimeTrackingInCurrentLap()
165+
{
166+
var timeProvider = new FakeTimeProvider();
167+
var stopwatch = new SplitSecondStopwatch(timeProvider);
168+
stopwatch.Start();
169+
170+
var elapsed1 = TimeSpan.FromSeconds(33.6);
171+
timeProvider.Advance(elapsed1);
172+
173+
stopwatch.Pause();
174+
175+
var elapsed2 = TimeSpan.FromSeconds(33.6);
176+
timeProvider.Advance(elapsed2);
177+
178+
stopwatch.Start();
37179

180+
var elapsed3 = TimeSpan.FromSeconds(33.6);
181+
timeProvider.Advance(elapsed3);
182+
183+
Assert.Equal(elapsed1 + elapsed3, stopwatch.CurrentLap);
184+
}
185+
38186
[Fact]
39-
public void StopChangesStateToStop()
187+
public void StopFromPausedStateChangesStateToReady()
40188
{
41189
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
42190
stopwatch.Start();
191+
stopwatch.Pause();
192+
43193
stopwatch.Stop();
44-
Assert.Equal(StopwatchState.Stopped, stopwatch.State);
194+
195+
Assert.Equal(StopwatchState.Ready, stopwatch.State);
45196
}
46197

47198
[Fact]
48-
public void StopAddsLapTime()
199+
public void StopFromPausedStateResetsCurrentLap()
49200
{
50-
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
201+
var timeProvider = new FakeTimeProvider();
202+
var stopwatch = new SplitSecondStopwatch(timeProvider);
51203
stopwatch.Start();
204+
stopwatch.Pause();
205+
52206
stopwatch.Stop();
53-
Assert.Equal(StopwatchState.Stopped, stopwatch.State);
207+
208+
Assert.Equal(TimeSpan.Zero, stopwatch.CurrentLap);
209+
}
210+
211+
[Fact]
212+
public void StopFromPausedStateAddsCurrentLapToPreviousLaps()
213+
{
214+
var timeProvider = new FakeTimeProvider();
215+
var stopwatch = new SplitSecondStopwatch(timeProvider);
216+
stopwatch.Start();
217+
218+
var elapsed = TimeSpan.FromSeconds(33.6);
219+
timeProvider.Advance(elapsed);
220+
stopwatch.Pause();
221+
222+
timeProvider.Advance(TimeSpan.FromSeconds(44.4));
223+
stopwatch.Stop();
224+
225+
var recordedLap = Assert.Single(stopwatch.PreviousLaps);
226+
Assert.Equal(elapsed, recordedLap);
227+
}
228+
229+
[Fact]
230+
public void CurrentLapDoesNotTrackElapsedTimeInReadyState()
231+
{
232+
var timeProvider = new FakeTimeProvider();
233+
var stopwatch = new SplitSecondStopwatch(timeProvider);
234+
235+
timeProvider.Advance(TimeSpan.FromSeconds(55.5));
236+
237+
Assert.Equal(TimeSpan.Zero, stopwatch.CurrentLap);
238+
}
239+
240+
[Fact]
241+
public void StartFromRunningStateIsInvalid()
242+
{
243+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
244+
stopwatch.Start();
245+
246+
Assert.Throws<InvalidOperationException>(() => stopwatch.Start());
247+
}
248+
249+
public void StopFromReadyStateIsInvalid()
250+
{
251+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
252+
Assert.Throws<InvalidOperationException>(() => stopwatch.Stop());
253+
}
254+
255+
public void PauseFromReadyStateIsInvalid()
256+
{
257+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
258+
Assert.Throws<InvalidOperationException>(() => stopwatch.Pause());
54259
}
55260
}

0 commit comments

Comments
 (0)