Created
February 21, 2019 11:57
-
-
Save brianegan/484c871754beac867d565d593f1152e3 to your computer and use it in GitHub Desktop.
Shows how to use a FakeStopwatch in a Widget test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter_test/flutter_test.dart'; | |
import 'package:quiver/async.dart'; | |
import 'package:quiver/testing/time.dart'; | |
void main() { | |
group('CounterTimer', () { | |
testWidgets('counts down from 10 to 0', (tester) async { | |
// Capture the current time | |
int now = DateTime.now().microsecondsSinceEpoch; | |
// Create a Timer | |
final timer = CountdownTimer( | |
Duration(seconds: 10), | |
Duration(milliseconds: 100), | |
stopwatch: FakeStopwatch( | |
// The function will simply return our fake "Now" time. | |
() => now, | |
Duration(seconds: 1).inMicroseconds, | |
), | |
); | |
// Create the Widget we'll rebuild over time | |
final widget = Directionality( | |
textDirection: TextDirection.ltr, | |
child: CountdownTimerRunner(countdownTimer: timer), | |
); | |
// Create a function that advances the current | |
Future<void> advanceAndPump([Duration duration = Duration.zero]) async { | |
// We advance the "now" time by the given duration. This simulates | |
// moving the stopwatch forward in time by that Duration. | |
now = now + duration.inMicroseconds; | |
await tester.pumpWidget(widget, duration); | |
} | |
// Build the Widget the first time and make sure it shows the right thing | |
await advanceAndPump(); | |
expect(find.text('10'), findsOneWidget); | |
// Wait 10 seconds to make sure the Widget is at 0 | |
await advanceAndPump(Duration(seconds: 10)); | |
expect(find.text('0'), findsOneWidget); | |
// Important: Cancel the Timer, or this test will throw an error that | |
// there are active async calls in progress! | |
timer.cancel(); | |
}); | |
}); | |
} | |
class CountdownTimerWidget extends StatefulWidget { | |
@override | |
_CountdownTimerWidgetState createState() => _CountdownTimerWidgetState(); | |
} | |
class _CountdownTimerWidgetState extends State<CountdownTimerWidget> { | |
CountdownTimer _countdownTimer; | |
@override | |
void dispose() { | |
_countdownTimer?.cancel(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
if (_countdownTimer == null) { | |
return CountdownTimerInitializer( | |
Duration.zero, | |
onDurationSelected: (Duration duration) { | |
setState(() { | |
_countdownTimer = | |
CountdownTimer(duration, Duration(milliseconds: 250)); | |
}); | |
}, | |
); | |
} | |
return CountdownTimerRunner( | |
countdownTimer: _countdownTimer, | |
); | |
} | |
} | |
typedef void OnDurationSelected(Duration duration); | |
class CountdownTimerInitializer extends StatefulWidget { | |
final Duration initialDuration; | |
final OnDurationSelected onDurationSelected; | |
CountdownTimerInitializer(this.initialDuration, | |
{@required this.onDurationSelected, Key key}) | |
: super(key: key); | |
@override | |
CountdownTimerInitializerState createState() { | |
return new CountdownTimerInitializerState(initialDuration); | |
} | |
} | |
class CountdownTimerInitializerState extends State<CountdownTimerInitializer> { | |
Duration _duration; | |
CountdownTimerInitializerState([Duration initialDuration = Duration.zero]) | |
: _duration = initialDuration; | |
void _increment() { | |
setState(() { | |
_duration = _duration + Duration(seconds: 1); | |
}); | |
} | |
void _decrement() { | |
setState(() { | |
var updatedDuration = _duration - Duration(seconds: 1); | |
if (updatedDuration <= Duration.zero) { | |
updatedDuration = Duration.zero; | |
} | |
_duration = updatedDuration; | |
}); | |
} | |
bool get _isValid { | |
return _duration > Duration.zero; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Column( | |
children: <Widget>[ | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Text( | |
_duration.inSeconds.toString(), | |
style: Theme.of(context).textTheme.display1, | |
key: ValueKey("configure-duration-text"), | |
), | |
), | |
Row( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
RaisedButton( | |
key: ValueKey("configure-decrement"), | |
onPressed: _isValid ? _decrement : null, | |
child: Text("-"), | |
), | |
RaisedButton( | |
key: ValueKey("configure-increment"), | |
onPressed: _increment, | |
child: Text("+"), | |
), | |
], | |
), | |
RaisedButton( | |
key: ValueKey("configure-start"), | |
onPressed: | |
_isValid ? () => widget.onDurationSelected(_duration) : null, | |
child: Text("Start"), | |
) | |
], | |
); | |
} | |
} | |
class CountdownTimerRunner extends StatelessWidget { | |
final CountdownTimer countdownTimer; | |
const CountdownTimerRunner({Key key, @required this.countdownTimer}) | |
: super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Column( | |
children: <Widget>[ | |
StreamBuilder<CountdownTimer>( | |
initialData: countdownTimer, | |
stream: countdownTimer, | |
builder: (context, snapshot) { | |
if (snapshot.hasData) { | |
return Text( | |
countdownTimer.remaining.inSeconds.toString(), | |
key: ValueKey("runner-duration-text"), | |
); | |
} else { | |
return Container(); | |
} | |
}, | |
), | |
], | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment