Add a Zone-scoped clock field
diff --git a/pkgs/clock/lib/clock.dart b/pkgs/clock/lib/clock.dart index 8915ff7..19d5efd 100644 --- a/pkgs/clock/lib/clock.dart +++ b/pkgs/clock/lib/clock.dart
@@ -13,6 +13,7 @@ // limitations under the License. export 'src/clock.dart'; +export 'src/default.dart'; /// Returns current time. @Deprecated("Pass around an instance of Clock instead.")
diff --git a/pkgs/clock/lib/src/clock.dart b/pkgs/clock/lib/src/clock.dart index d11ed57..e518522 100644 --- a/pkgs/clock/lib/src/clock.dart +++ b/pkgs/clock/lib/src/clock.dart
@@ -22,6 +22,10 @@ /// (or [now()]) is defined by a function that returns a [DateTime]. By /// supplying your own time function or using [new Clock.fixed], you can control /// exactly what time a [Clock] returns and base your test expectations on that. +/// +/// Most users should use the top-level [clock] field, which provides access to +/// a default implementation of [Clock] which can be overridden using +/// [withClock]. class Clock { /// The function that's called to determine this clock's notion of the current /// time.
diff --git a/pkgs/clock/lib/src/default.dart b/pkgs/clock/lib/src/default.dart new file mode 100644 index 0000000..b380927 --- /dev/null +++ b/pkgs/clock/lib/src/default.dart
@@ -0,0 +1,48 @@ +// Copyright 2018 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:async'; + +import 'clock.dart'; + +/// The key for the [Zone] value that controls the current implementation of +/// [clock]. +final _clockKey = new Object(); + +/// The key for the [Zone] value that controls whether nested zones can override +/// [clock]. +final _isFinalKey = new Object(); + +/// The default implementation of [clock] for the current [Zone]. +/// +/// This defaults to the system clock. It can be set within a zone using +/// [withClock]. +Clock get clock => Zone.current[_clockKey] ?? const Clock(); + +/// Runs [callback] with the given value for the top-level [clock] field. +/// +/// This is [Zone]-scoped, so asynchronous callbacks spawned within [callback] +/// will also use the new value for [clock]. +/// +/// If [isFinal] is `true`, calls to [withClock] within [callback] will throw a +/// [StateError]. However, this parameter is deprecated and should be avoided. +T withClock<T>(Clock clock, T callback(), {@deprecated bool isFinal: false}) { + if (Zone.current[_isFinalKey] ?? false) { + throw new StateError( + "Cannot call withClock() within a call to withClock(isFinal: true)."); + } + + return runZoned(callback, + zoneValues: {_clockKey: clock, _isFinalKey: isFinal}); +}
diff --git a/pkgs/clock/pubspec.yaml b/pkgs/clock/pubspec.yaml index 5ebecf2..4d1449d 100644 --- a/pkgs/clock/pubspec.yaml +++ b/pkgs/clock/pubspec.yaml
@@ -11,4 +11,4 @@ meta: '>=0.9.0 <2.0.0' dev_dependencies: - test: '^0.12.0' + test: '^0.12.28'
diff --git a/pkgs/clock/test/clock_test.dart b/pkgs/clock/test/clock_test.dart index 633cdb9..3d2d1b2 100644 --- a/pkgs/clock/test/clock_test.dart +++ b/pkgs/clock/test/clock_test.dart
@@ -16,20 +16,12 @@ import 'package:test/test.dart'; -/// A utility function for tersely constructing a [DateTime] with no time -/// component. -DateTime _date(int year, [int month, int day]) => - new DateTime(year, month ?? 1, day ?? 1); - -/// Return a clock that always returns a date with the given [year], [month], -/// and [day]. -Clock _fixed(int year, [int month, int day]) => - new Clock.fixed(_date(year, month, day)); +import 'utils.dart'; main() { Clock clock; setUp(() { - clock = new Clock.fixed(_date(2013)); + clock = new Clock.fixed(date(2013)); }); test('should return a non-null value from system clock', () { @@ -50,24 +42,24 @@ }); test('should return time provided by a custom function', () { - var time = _date(2013); + var time = date(2013); var fixedClock = new Clock(() => time); - expect(fixedClock.now(), _date(2013)); + expect(fixedClock.now(), date(2013)); - time = _date(2014); - expect(fixedClock.now(), _date(2014)); + time = date(2014); + expect(fixedClock.now(), date(2014)); }); test('should return fixed time', () { - expect(new Clock.fixed(_date(2013)).now(), _date(2013)); + expect(new Clock.fixed(date(2013)).now(), date(2013)); }); test('should return time Duration ago', () { - expect(clock.agoBy(const Duration(days: 366)), _date(2012)); + expect(clock.agoBy(const Duration(days: 366)), date(2012)); }); test('should return time Duration from now', () { - expect(clock.fromNowBy(const Duration(days: 365)), _date(2014)); + expect(clock.fromNowBy(const Duration(days: 365)), date(2014)); }); test('should return time parts ago', () { @@ -135,86 +127,86 @@ }); test('should return time days ago', () { - expect(clock.daysAgo(10), _date(2012, 12, 22)); + expect(clock.daysAgo(10), date(2012, 12, 22)); }); test('should return time days from now', () { - expect(clock.daysFromNow(3), _date(2013, 1, 4)); + expect(clock.daysFromNow(3), date(2013, 1, 4)); }); test('should return time months ago on the same date', () { - expect(clock.monthsAgo(1), _date(2012, 12, 1)); - expect(clock.monthsAgo(2), _date(2012, 11, 1)); - expect(clock.monthsAgo(3), _date(2012, 10, 1)); - expect(clock.monthsAgo(4), _date(2012, 9, 1)); + expect(clock.monthsAgo(1), date(2012, 12, 1)); + expect(clock.monthsAgo(2), date(2012, 11, 1)); + expect(clock.monthsAgo(3), date(2012, 10, 1)); + expect(clock.monthsAgo(4), date(2012, 9, 1)); }); test('should return time months from now on the same date', () { - expect(clock.monthsFromNow(1), _date(2013, 2, 1)); - expect(clock.monthsFromNow(2), _date(2013, 3, 1)); - expect(clock.monthsFromNow(3), _date(2013, 4, 1)); - expect(clock.monthsFromNow(4), _date(2013, 5, 1)); + expect(clock.monthsFromNow(1), date(2013, 2, 1)); + expect(clock.monthsFromNow(2), date(2013, 3, 1)); + expect(clock.monthsFromNow(3), date(2013, 4, 1)); + expect(clock.monthsFromNow(4), date(2013, 5, 1)); }); test('should go from 2013-05-31 to 2012-11-30', () { - expect(_fixed(2013, 5, 31).monthsAgo(6), _date(2012, 11, 30)); + expect(fixed(2013, 5, 31).monthsAgo(6), date(2012, 11, 30)); }); test('should go from 2013-03-31 to 2013-02-28 (common year)', () { - expect(_fixed(2013, 3, 31).monthsAgo(1), _date(2013, 2, 28)); + expect(fixed(2013, 3, 31).monthsAgo(1), date(2013, 2, 28)); }); test('should go from 2013-05-31 to 2013-02-28 (common year)', () { - expect(_fixed(2013, 5, 31).monthsAgo(3), _date(2013, 2, 28)); + expect(fixed(2013, 5, 31).monthsAgo(3), date(2013, 2, 28)); }); test('should go from 2004-03-31 to 2004-02-29 (leap year)', () { - expect(_fixed(2004, 3, 31).monthsAgo(1), _date(2004, 2, 29)); + expect(fixed(2004, 3, 31).monthsAgo(1), date(2004, 2, 29)); }); test('should go from 2013-03-31 to 2013-06-30', () { - expect(_fixed(2013, 3, 31).monthsFromNow(3), _date(2013, 6, 30)); + expect(fixed(2013, 3, 31).monthsFromNow(3), date(2013, 6, 30)); }); test('should go from 2003-12-31 to 2004-02-29 (common to leap)', () { - expect(_fixed(2003, 12, 31).monthsFromNow(2), _date(2004, 2, 29)); + expect(fixed(2003, 12, 31).monthsFromNow(2), date(2004, 2, 29)); }); test('should go from 2004-02-29 to 2003-02-28 by year', () { - expect(_fixed(2004, 2, 29).yearsAgo(1), _date(2003, 2, 28)); + expect(fixed(2004, 2, 29).yearsAgo(1), date(2003, 2, 28)); }); test('should go from 2004-02-29 to 2003-02-28 by month', () { - expect(_fixed(2004, 2, 29).monthsAgo(12), _date(2003, 2, 28)); + expect(fixed(2004, 2, 29).monthsAgo(12), date(2003, 2, 28)); }); test('should go from 2004-02-29 to 2005-02-28 by year', () { - expect(_fixed(2004, 2, 29).yearsFromNow(1), _date(2005, 2, 28)); + expect(fixed(2004, 2, 29).yearsFromNow(1), date(2005, 2, 28)); }); test('should go from 2004-02-29 to 2005-02-28 by month', () { - expect(_fixed(2004, 2, 29).monthsFromNow(12), _date(2005, 2, 28)); + expect(fixed(2004, 2, 29).monthsFromNow(12), date(2005, 2, 28)); }); test('should return time years ago on the same date', () { - expect(clock.yearsAgo(1), _date(2012, 1, 1)); // leap year - expect(clock.yearsAgo(2), _date(2011, 1, 1)); - expect(clock.yearsAgo(3), _date(2010, 1, 1)); - expect(clock.yearsAgo(4), _date(2009, 1, 1)); - expect(clock.yearsAgo(5), _date(2008, 1, 1)); // leap year - expect(clock.yearsAgo(6), _date(2007, 1, 1)); - expect(clock.yearsAgo(30), _date(1983, 1, 1)); - expect(clock.yearsAgo(2013), _date(0, 1, 1)); + expect(clock.yearsAgo(1), date(2012, 1, 1)); // leap year + expect(clock.yearsAgo(2), date(2011, 1, 1)); + expect(clock.yearsAgo(3), date(2010, 1, 1)); + expect(clock.yearsAgo(4), date(2009, 1, 1)); + expect(clock.yearsAgo(5), date(2008, 1, 1)); // leap year + expect(clock.yearsAgo(6), date(2007, 1, 1)); + expect(clock.yearsAgo(30), date(1983, 1, 1)); + expect(clock.yearsAgo(2013), date(0, 1, 1)); }); test('should return time years from now on the same date', () { - expect(clock.yearsFromNow(1), _date(2014, 1, 1)); - expect(clock.yearsFromNow(2), _date(2015, 1, 1)); - expect(clock.yearsFromNow(3), _date(2016, 1, 1)); - expect(clock.yearsFromNow(4), _date(2017, 1, 1)); - expect(clock.yearsFromNow(5), _date(2018, 1, 1)); - expect(clock.yearsFromNow(6), _date(2019, 1, 1)); - expect(clock.yearsFromNow(30), _date(2043, 1, 1)); - expect(clock.yearsFromNow(1000), _date(3013, 1, 1)); + expect(clock.yearsFromNow(1), date(2014, 1, 1)); + expect(clock.yearsFromNow(2), date(2015, 1, 1)); + expect(clock.yearsFromNow(3), date(2016, 1, 1)); + expect(clock.yearsFromNow(4), date(2017, 1, 1)); + expect(clock.yearsFromNow(5), date(2018, 1, 1)); + expect(clock.yearsFromNow(6), date(2019, 1, 1)); + expect(clock.yearsFromNow(30), date(2043, 1, 1)); + expect(clock.yearsFromNow(1000), date(3013, 1, 1)); }); }
diff --git a/pkgs/clock/test/default_test.dart b/pkgs/clock/test/default_test.dart new file mode 100644 index 0000000..edcb7cd --- /dev/null +++ b/pkgs/clock/test/default_test.dart
@@ -0,0 +1,79 @@ +// Copyright 2018 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the 'License'); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:async'; + +import 'package:clock/clock.dart'; + +import 'package:test/test.dart'; + +import 'utils.dart'; + +void main() { + test("the default clock returns the system time", () { + expect(new DateTime.now().difference(clock.now()).inMilliseconds.abs(), + lessThan(100)); + }); + + group("withClock()", () { + group("overrides the clock", () { + test("synchronously", () { + var time = date(1990, 11, 8); + withClock(new Clock(() => time), () { + expect(clock.now(), equals(time)); + time = date(2016, 6, 26); + expect(clock.now(), equals(time)); + }); + }); + + test("asynchronously", () { + var time = date(1990, 11, 8); + withClock(new Clock.fixed(time), () { + expect(new Future(() async { + expect(clock.now(), equals(time)); + }), completes); + }); + }); + + test("within another withClock() call", () { + var outerTime = date(1990, 11, 8); + withClock(new Clock.fixed(outerTime), () { + expect(clock.now(), equals(outerTime)); + + var innerTime = date(2016, 11, 8); + withClock(new Clock.fixed(innerTime), () { + expect(clock.now(), equals(innerTime)); + expect(new Future(() async { + expect(clock.now(), equals(innerTime)); + }), completes); + }); + + expect(clock.now(), equals(outerTime)); + }); + }); + }); + + test("with isFinal: true doesn't allow nested calls", () { + var outerTime = date(1990, 11, 8); + withClock(new Clock.fixed(outerTime), () { + expect(clock.now(), equals(outerTime)); + + expect( + () => withClock(fixed(2016, 11, 8), neverCalled), throwsStateError); + + expect(clock.now(), equals(outerTime)); + }, isFinal: true); + }); + }); +}
diff --git a/pkgs/clock/test/utils.dart b/pkgs/clock/test/utils.dart new file mode 100644 index 0000000..17c07f3 --- /dev/null +++ b/pkgs/clock/test/utils.dart
@@ -0,0 +1,25 @@ +// Copyright 2018 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:clock/clock.dart'; + +/// A utility function for tersely constructing a [DateTime] with no time +/// component. +DateTime date(int year, [int month, int day]) => + new DateTime(year, month ?? 1, day ?? 1); + +/// Returns a clock that always returns a date with the given [year], [month], +/// and [day]. +Clock fixed(int year, [int month, int day]) => + new Clock.fixed(date(year, month, day));