Skip to content

Commit 7df041c

Browse files
Add last-lap exercise
1 parent 9210b05 commit 7df041c

File tree

2 files changed

+239
-27
lines changed

2 files changed

+239
-27
lines changed
Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,59 @@
11
public enum StopwatchState
22
{
33
Ready,
4-
Running,
5-
Paused,
6-
Stopped
4+
Running
75
}
86

97
public class SplitSecondStopwatch(TimeProvider time)
108
{
11-
public StopwatchState State { get; private set; }
9+
private readonly List<TimeSpan> _laps = new();
10+
private readonly List<TimeSpan> _splits = new();
11+
private DateTimeOffset? _splitStart;
12+
13+
private TimeSpan PreviousSplits => _splits.Aggregate(TimeSpan.Zero, (total, split) => total + split);
14+
private TimeSpan CurrentSplit => _splitStart is {} start ? time.GetUtcNow() - start : TimeSpan.Zero;
15+
16+
public StopwatchState State { get; private set; } = StopwatchState.Ready;
17+
public List<TimeSpan> Laps => Split == TimeSpan.Zero ? _laps : _laps.Append(Split).ToList();
18+
public TimeSpan Split => CurrentSplit + PreviousSplits;
19+
public TimeSpan Total => Split + Laps.Aggregate(TimeSpan.Zero, (total, split) => total + split);
1220

1321
public void Start()
1422
{
15-
throw new NotImplementedException();
23+
if (State != StopwatchState.Ready)
24+
throw new InvalidOperationException("Can't start a stopwatch that is not stopped.");
25+
26+
_splitStart = time.GetUtcNow();
27+
State = StopwatchState.Running;
1628
}
1729

1830
public void Stop()
1931
{
20-
throw new NotImplementedException();
21-
}
32+
if (State != StopwatchState.Running)
33+
throw new InvalidOperationException("Can't stop a stopwatch that is not started.");
2234

23-
public void Split()
35+
_splits.Add(CurrentSplit);
36+
_splitStart = null;
37+
State = StopwatchState.Ready;
38+
}
39+
40+
public void Reset()
2441
{
25-
throw new NotImplementedException();
42+
if (State != StopwatchState.Ready)
43+
throw new InvalidOperationException("Can't reset a stopwatch that is not stopped.");
44+
45+
_splits.Clear();
46+
_splitStart = null;
47+
State = StopwatchState.Ready;
2648
}
27-
28-
public void Pause()
49+
50+
public void Lap()
2951
{
30-
throw new NotImplementedException();
52+
if (State != StopwatchState.Running)
53+
throw new InvalidOperationException("Can't lap a stopwatch that is not started stopped.");
54+
55+
_laps.Add(Split);
56+
_splits.Clear();
57+
_splitStart = time.GetUtcNow();
3158
}
3259
}

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

Lines changed: 200 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,32 @@
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+
12+
[Fact]
13+
public void LapsIsInitiallyEmpty()
14+
{
15+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
16+
Assert.Empty(stopwatch.Laps);
17+
}
18+
19+
[Fact]
20+
public void SplitIsInitiallyZero()
21+
{
22+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
23+
Assert.Equal(TimeSpan.Zero, stopwatch.Split);
24+
}
25+
26+
[Fact]
27+
public void TotalIsInitiallyZero()
28+
{
29+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
30+
Assert.Equal(TimeSpan.Zero, stopwatch.Split);
31+
}
1132

1233
[Fact]
1334
public void StartChangesStateToRunning()
@@ -16,40 +37,204 @@ public void StartChangesStateToRunning()
1637
stopwatch.Start();
1738
Assert.Equal(StopwatchState.Running, stopwatch.State);
1839
}
19-
40+
2041
[Fact]
21-
public void PauseChangesStateToPaused()
42+
public void StartDoesNotChangeLaps()
2243
{
2344
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
2445
stopwatch.Start();
25-
stopwatch.Pause();
26-
Assert.Equal(StopwatchState.Paused, stopwatch.State);
46+
Assert.Empty(stopwatch.Laps);
2747
}
28-
48+
2949
[Fact]
30-
public void SplitKeepsStateAsRunning()
50+
public void StartStartsTimeTrackingInSplit()
51+
{
52+
var timeProvider = new FakeTimeProvider();
53+
var stopwatch = new SplitSecondStopwatch(timeProvider);
54+
stopwatch.Start();
55+
56+
var elapsed = TimeSpan.FromSeconds(55.5);
57+
timeProvider.Advance(elapsed);
58+
59+
Assert.Equal(elapsed, stopwatch.Split);
60+
}
61+
62+
[Fact]
63+
public void StartStartsTimeTrackingInTotal()
64+
{
65+
var timeProvider = new FakeTimeProvider();
66+
var stopwatch = new SplitSecondStopwatch(timeProvider);
67+
stopwatch.Start();
68+
69+
var elapsed = TimeSpan.FromSeconds(55.5);
70+
timeProvider.Advance(elapsed);
71+
72+
Assert.Equal(elapsed, stopwatch.Split);
73+
}
74+
75+
[Fact]
76+
public void CannotStartFromRunningState()
3177
{
3278
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
3379
stopwatch.Start();
34-
stopwatch.Split();
35-
Assert.Equal(StopwatchState.Running, stopwatch.State);
80+
81+
Assert.Throws<InvalidOperationException>(() => stopwatch.Start());
3682
}
37-
83+
3884
[Fact]
39-
public void StopChangesStateToStop()
85+
public void StopFromRunningStateChangesStateToReady()
4086
{
4187
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
4288
stopwatch.Start();
89+
4390
stopwatch.Stop();
44-
Assert.Equal(StopwatchState.Stopped, stopwatch.State);
91+
92+
Assert.Equal(StopwatchState.Ready, stopwatch.State);
4593
}
46-
94+
4795
[Fact]
48-
public void StopAddsLapTime()
96+
public void StopDoesNotChangeLaps()
4997
{
5098
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
5199
stopwatch.Start();
100+
52101
stopwatch.Stop();
53-
Assert.Equal(StopwatchState.Stopped, stopwatch.State);
102+
103+
Assert.Empty(stopwatch.Laps);
104+
}
105+
106+
[Fact]
107+
public void StopStopsTimeTrackingInSplit()
108+
{
109+
var timeProvider = new FakeTimeProvider();
110+
var stopwatch = new SplitSecondStopwatch(timeProvider);
111+
stopwatch.Start();
112+
113+
var elapsed1 = TimeSpan.FromSeconds(22.5);
114+
timeProvider.Advance(elapsed1);
115+
116+
stopwatch.Stop();
117+
118+
var elapsed2 = TimeSpan.FromSeconds(17.9);
119+
timeProvider.Advance(elapsed2);
120+
121+
Assert.Equal(elapsed1, stopwatch.Split);
122+
}
123+
124+
[Fact]
125+
public void StopStopsTimeTrackingInTotal()
126+
{
127+
var timeProvider = new FakeTimeProvider();
128+
var stopwatch = new SplitSecondStopwatch(timeProvider);
129+
stopwatch.Start();
130+
131+
var elapsed1 = TimeSpan.FromSeconds(22.5);
132+
timeProvider.Advance(elapsed1);
133+
134+
stopwatch.Stop();
135+
136+
var elapsed2 = TimeSpan.FromSeconds(17.9);
137+
timeProvider.Advance(elapsed2);
138+
139+
Assert.Equal(elapsed1, stopwatch.Total);
140+
}
141+
142+
[Fact]
143+
public void CannotStopFromReadyState()
144+
{
145+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
146+
147+
Assert.Throws<InvalidOperationException>(() => stopwatch.Stop());
148+
}
149+
150+
[Fact]
151+
public void ResetClearsLaps()
152+
{
153+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
154+
stopwatch.Start();
155+
stopwatch.Stop();
156+
157+
stopwatch.Reset();
158+
159+
Assert.Empty(stopwatch.Laps);
160+
}
161+
162+
[Fact]
163+
public void ResetSetsSplitToZero()
164+
{
165+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
166+
stopwatch.Start();
167+
stopwatch.Stop();
168+
169+
stopwatch.Reset();
170+
171+
Assert.Equal(TimeSpan.Zero, stopwatch.Split);
172+
}
173+
174+
[Fact]
175+
public void ResetSetsTotalToZero()
176+
{
177+
var stopwatch = new SplitSecondStopwatch(new FakeTimeProvider());
178+
stopwatch.Start();
179+
stopwatch.Stop();
180+
181+
stopwatch.Reset();
182+
183+
Assert.Equal(TimeSpan.Zero, stopwatch.Total);
184+
}
185+
186+
[Fact]
187+
public void LapAddsSplitToLaps()
188+
{
189+
var timeProvider = new FakeTimeProvider();
190+
var stopwatch = new SplitSecondStopwatch(timeProvider);
191+
stopwatch.Start();
192+
193+
var elapsed1 = TimeSpan.FromSeconds(22.5);
194+
timeProvider.Advance(elapsed1);
195+
196+
stopwatch.Lap();
197+
198+
var elapsed2 = TimeSpan.FromSeconds(17.9);
199+
timeProvider.Advance(elapsed2);
200+
201+
var lap = Assert.Single(stopwatch.Laps);
202+
Assert.Equal(elapsed1, lap);
203+
}
204+
205+
[Fact]
206+
public void LapResetsSplitStartTimeToCurrentTime()
207+
{
208+
var timeProvider = new FakeTimeProvider();
209+
var stopwatch = new SplitSecondStopwatch(timeProvider);
210+
stopwatch.Start();
211+
212+
var elapsed1 = TimeSpan.FromSeconds(22.5);
213+
timeProvider.Advance(elapsed1);
214+
215+
stopwatch.Lap();
216+
217+
var elapsed2 = TimeSpan.FromSeconds(17.9);
218+
timeProvider.Advance(elapsed2);
219+
220+
Assert.Equal(elapsed2, stopwatch.Split);
221+
}
222+
223+
[Fact]
224+
public void LapDoesNotStopTimeTrackingInTotal()
225+
{
226+
var timeProvider = new FakeTimeProvider();
227+
var stopwatch = new SplitSecondStopwatch(timeProvider);
228+
stopwatch.Start();
229+
230+
var elapsed1 = TimeSpan.FromSeconds(22.5);
231+
timeProvider.Advance(elapsed1);
232+
233+
stopwatch.Lap();
234+
235+
var elapsed2 = TimeSpan.FromSeconds(17.9);
236+
timeProvider.Advance(elapsed2);
237+
238+
Assert.Equal(elapsed1 + elapsed2, stopwatch.Total);
54239
}
55240
}

0 commit comments

Comments
 (0)