| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:collection/collection.dart'; |
| import 'package:devtools_app/src/shared/primitives/utils.dart'; |
| import 'package:devtools_app/src/shared/utils.dart'; |
| import 'package:devtools_app_shared/ui.dart'; |
| import 'package:devtools_app_shared/utils.dart'; |
| import 'package:devtools_shared/devtools_test_utils.dart'; |
| import 'package:devtools_test/devtools_test.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:provider/provider.dart'; |
| |
| void main() { |
| group('utils', () { |
| test('prettyPrintBytes', () { |
| const int kb = 1024; |
| const int mb = 1024 * kb; |
| |
| expect( |
| prettyPrintBytes( |
| 51, |
| kbFractionDigits: 1, |
| includeUnit: true, |
| ), |
| '51 B', |
| ); |
| expect( |
| prettyPrintBytes( |
| 52, |
| kbFractionDigits: 1, |
| includeUnit: true, |
| ), |
| '0.1 KB', |
| ); |
| |
| expect(prettyPrintBytes(kb), '1'); |
| expect(prettyPrintBytes(kb + 100, kbFractionDigits: 1), '1.1'); |
| expect(prettyPrintBytes(kb + 150, kbFractionDigits: 2), '1.15'); |
| expect(prettyPrintBytes(kb, includeUnit: true), '1 KB'); |
| expect(prettyPrintBytes(kb * 1000, includeUnit: true), '1,000 KB'); |
| |
| expect(prettyPrintBytes(mb), '1.0'); |
| expect(prettyPrintBytes(mb + kb * 100), '1.1'); |
| expect(prettyPrintBytes(mb + kb * 150, mbFractionDigits: 2), '1.15'); |
| expect(prettyPrintBytes(mb, includeUnit: true), '1.0 MB'); |
| expect(prettyPrintBytes(mb - kb, includeUnit: true), '1,023 KB'); |
| }); |
| |
| test('printKb', () { |
| const int kb = 1024; |
| |
| expect(printKB(0), '0'); |
| expect(printKB(1), '1'); |
| expect(printKB(kb - 1), '1'); |
| expect(printKB(kb), '1'); |
| expect(printKB(kb + 1), '2'); |
| expect(printKB(2000), '2'); |
| }); |
| |
| test('printMb', () { |
| const int mb = 1024 * 1024; |
| |
| expect(printMB(10 * mb, fractionDigits: 0), '10'); |
| expect(printMB(10 * mb), '10.0'); |
| expect(printMB(10 * mb, fractionDigits: 2), '10.00'); |
| |
| expect(printMB(1000 * mb, fractionDigits: 0), '1000'); |
| expect(printMB(1000 * mb), '1000.0'); |
| expect(printMB(1000 * mb, fractionDigits: 2), '1000.00'); |
| }); |
| |
| group('durationText', () { |
| test('infers unit based on duration', () { |
| expect( |
| durationText(Duration.zero), |
| equals('0 μs'), |
| ); |
| expect( |
| durationText(const Duration(microseconds: 100)), |
| equals('0.1 ms'), |
| ); |
| expect( |
| durationText(const Duration(microseconds: 99)), |
| equals('99 μs'), |
| ); |
| expect( |
| durationText(const Duration(microseconds: 1000)), |
| equals('1.0 ms'), |
| ); |
| expect( |
| durationText(const Duration(microseconds: 999900)), |
| equals('999.9 ms'), |
| ); |
| expect( |
| durationText(const Duration(microseconds: 1000000)), |
| equals('1.0 s'), |
| ); |
| expect( |
| durationText(const Duration(microseconds: 9000000)), |
| equals('9.0 s'), |
| ); |
| }); |
| |
| test('displays proper number of fraction digits', () { |
| expect( |
| durationText(const Duration(microseconds: 99)), |
| equals('99 μs'), |
| ); |
| expect( |
| durationText( |
| const Duration(microseconds: 99), |
| // Should ignore this since this will be displayed in microseconds. |
| fractionDigits: 3, |
| ), |
| equals('99 μs'), |
| ); |
| expect( |
| durationText(const Duration(microseconds: 3111)), |
| equals('3.1 ms'), |
| ); |
| expect( |
| durationText(const Duration(microseconds: 3159), fractionDigits: 2), |
| equals('3.16 ms'), |
| ); |
| expect( |
| durationText(const Duration(microseconds: 3111), fractionDigits: 3), |
| equals('3.111 ms'), |
| ); |
| }); |
| |
| test('does not include unit when specified', () { |
| expect( |
| durationText( |
| const Duration(microseconds: 1000), |
| includeUnit: false, |
| ), |
| equals('1.0'), |
| ); |
| expect( |
| durationText( |
| const Duration(milliseconds: 10000), |
| includeUnit: false, |
| unit: DurationDisplayUnit.seconds, |
| ), |
| equals('10.0'), |
| ); |
| }); |
| |
| test('does not allow rounding to zero when specified', () { |
| // Setting [allowRoundingToZero] to false without specifying a unit |
| // throws an assertion error. |
| expect( |
| () { |
| durationText(Duration.zero, allowRoundingToZero: false); |
| }, |
| throwsAssertionError, |
| ); |
| |
| // Displays zero for true zero values. |
| expect( |
| durationText( |
| Duration.zero, |
| allowRoundingToZero: false, |
| unit: DurationDisplayUnit.micros, |
| ), |
| equals('0 μs'), |
| ); |
| expect( |
| durationText( |
| Duration.zero, |
| allowRoundingToZero: false, |
| unit: DurationDisplayUnit.milliseconds, |
| ), |
| equals('0.0 ms'), |
| ); |
| expect( |
| durationText( |
| Duration.zero, |
| allowRoundingToZero: false, |
| unit: DurationDisplayUnit.seconds, |
| ), |
| equals('0.0 s'), |
| ); |
| |
| // Displays less than text for close-to-zero values. |
| expect( |
| durationText( |
| const Duration(microseconds: 1), |
| allowRoundingToZero: false, |
| unit: DurationDisplayUnit.milliseconds, |
| ), |
| equals('< 0.1 ms'), |
| ); |
| expect( |
| durationText( |
| const Duration(microseconds: 1), |
| allowRoundingToZero: false, |
| unit: DurationDisplayUnit.seconds, |
| ), |
| equals('< 0.1 s'), |
| ); |
| |
| // Only displays less than text values that would round to zero. |
| expect( |
| durationText( |
| const Duration(microseconds: 49), |
| allowRoundingToZero: false, |
| unit: DurationDisplayUnit.milliseconds, |
| ), |
| equals('< 0.1 ms'), |
| ); |
| expect( |
| durationText( |
| const Duration(microseconds: 50), |
| allowRoundingToZero: false, |
| unit: DurationDisplayUnit.milliseconds, |
| ), |
| equals('0.1 ms'), |
| ); |
| |
| // Displays properly with fraction digits. |
| expect( |
| durationText( |
| const Duration(microseconds: 1), |
| fractionDigits: 3, |
| allowRoundingToZero: false, |
| unit: DurationDisplayUnit.milliseconds, |
| ), |
| equals('< 0.001 ms'), |
| ); |
| }); |
| }); |
| |
| test('nullSafeMin', () { |
| expect(nullSafeMin<int>(1, 2), equals(1)); |
| expect(nullSafeMin<int>(1, null), equals(1)); |
| expect(nullSafeMin<int>(null, 2), equals(2)); |
| expect(nullSafeMin<int>(null, null), equals(null)); |
| }); |
| |
| test('nullSafeMin', () { |
| expect(nullSafeMax<int>(1, 2), equals(2)); |
| expect(nullSafeMax<int>(1, null), equals(1)); |
| expect(nullSafeMax<int>(null, 2), equals(2)); |
| expect(nullSafeMax<int>(null, null), equals(null)); |
| }); |
| |
| test('log2', () { |
| expect(log2(1), equals(0)); |
| expect(log2(1.5), equals(0)); |
| expect(log2(2), equals(1)); |
| expect(log2(3), equals(1)); |
| expect(log2(4), equals(2)); |
| }); |
| |
| test('roundToNearestPow10', () { |
| expect(roundToNearestPow10(1), equals(1)); |
| expect(roundToNearestPow10(2), equals(10)); |
| expect(roundToNearestPow10(10), equals(10)); |
| expect(roundToNearestPow10(11), equals(100)); |
| expect(roundToNearestPow10(189), equals(1000)); |
| expect(roundToNearestPow10(6581), equals(10000)); |
| }); |
| |
| test('executeWithDelay', () async { |
| const delayMs = 500; |
| int n = 1; |
| int start = DateTime.now().millisecondsSinceEpoch; |
| int? end; |
| |
| // Condition n >= 2 is false, so we should execute with a delay. |
| executeWithDelay( |
| const Duration(milliseconds: 500), |
| () { |
| n++; |
| end = DateTime.now().millisecondsSinceEpoch; |
| }, |
| executeNow: n >= 2, |
| ); |
| |
| expect(n, equals(1)); |
| expect(end, isNull); |
| await Future.delayed(const Duration(milliseconds: 250)); |
| expect(n, equals(1)); |
| expect(end, isNull); |
| await Future.delayed(const Duration(milliseconds: 250)); |
| expect(n, equals(2)); |
| expect(end, isNotNull); |
| |
| // 1000ms is arbitrary. We want to ensure it doesn't run in less time than |
| // we requested (checked above), but we don't want to be too strict because |
| // shared CI CPUs can be slow. |
| const epsilonMs = 1000; |
| expect((end! - start - delayMs).abs(), lessThan(epsilonMs)); |
| |
| // Condition n >= 2 is true, so we should not execute with a delay. |
| end = null; |
| start = DateTime.now().millisecondsSinceEpoch; |
| executeWithDelay( |
| const Duration(milliseconds: 500), |
| () { |
| n++; |
| end = DateTime.now().millisecondsSinceEpoch; |
| }, |
| executeNow: true, |
| ); |
| expect(n, equals(3)); |
| expect(end, isNotNull); |
| // 400ms is arbitrary. It is less than 500, which is what matters. This |
| // can be increased if this test starts to flake. |
| expect(end! - start, lessThan(400)); |
| }); |
| |
| test('timeout', () async { |
| int value = 0; |
| Future<int> operation() async { |
| await Future.delayed(const Duration(milliseconds: 200)); |
| return ++value; |
| } |
| |
| expect(value, equals(0)); |
| |
| var result = await timeout<int>(operation(), 100); |
| await delay(); |
| expect(value, equals(1)); |
| expect(result, isNull); |
| |
| result = await timeout<int>(operation(), 500); |
| await delay(); |
| expect(value, equals(2)); |
| expect(result, equals(2)); |
| }); |
| |
| group('TimeRange', () { |
| test('toString', () { |
| final timeRange = TimeRange(); |
| |
| expect(timeRange.toString(), equals('[null μs - null μs]')); |
| |
| timeRange |
| ..start = const Duration(microseconds: 1000) |
| ..end = const Duration(microseconds: 8000); |
| |
| expect(timeRange.duration.inMicroseconds, equals(7000)); |
| expect(timeRange.toString(), equals('[1000 μs - 8000 μs]')); |
| expect( |
| timeRange.toString(unit: TimeUnit.milliseconds), |
| equals('[1 ms - 8 ms]'), |
| ); |
| }); |
| |
| test('overlaps', () { |
| final t = TimeRange() |
| ..start = const Duration(milliseconds: 100) |
| ..end = const Duration(milliseconds: 200); |
| final overlapBeginning = TimeRange() |
| ..start = const Duration(milliseconds: 50) |
| ..end = const Duration(milliseconds: 150); |
| final overlapMiddle = TimeRange() |
| ..start = const Duration(milliseconds: 125) |
| ..end = const Duration(milliseconds: 175); |
| final overlapEnd = TimeRange() |
| ..start = const Duration(milliseconds: 150) |
| ..end = const Duration(milliseconds: 250); |
| final overlapAll = TimeRange() |
| ..start = const Duration(milliseconds: 50) |
| ..end = const Duration(milliseconds: 250); |
| final noOverlap = TimeRange() |
| ..start = const Duration(milliseconds: 300) |
| ..end = const Duration(milliseconds: 400); |
| |
| expect(t.overlaps(t), isTrue); |
| expect(t.overlaps(overlapBeginning), isTrue); |
| expect(t.overlaps(overlapMiddle), isTrue); |
| expect(t.overlaps(overlapEnd), isTrue); |
| expect(t.overlaps(overlapAll), isTrue); |
| expect(t.overlaps(noOverlap), isFalse); |
| }); |
| |
| test('containsRange', () { |
| final t = TimeRange() |
| ..start = const Duration(milliseconds: 100) |
| ..end = const Duration(milliseconds: 200); |
| final containsStart = TimeRange() |
| ..start = const Duration(milliseconds: 50) |
| ..end = const Duration(milliseconds: 150); |
| final containsStartAndEnd = TimeRange() |
| ..start = const Duration(milliseconds: 125) |
| ..end = const Duration(milliseconds: 175); |
| final containsEnd = TimeRange() |
| ..start = const Duration(milliseconds: 150) |
| ..end = const Duration(milliseconds: 250); |
| final invertedContains = TimeRange() |
| ..start = const Duration(milliseconds: 50) |
| ..end = const Duration(milliseconds: 250); |
| final containsNeither = TimeRange() |
| ..start = const Duration(milliseconds: 300) |
| ..end = const Duration(milliseconds: 400); |
| |
| expect(t.containsRange(containsStart), isFalse); |
| expect(t.containsRange(containsStartAndEnd), isTrue); |
| expect(t.containsRange(containsEnd), isFalse); |
| expect(t.containsRange(invertedContains), isFalse); |
| expect(t.containsRange(containsNeither), isFalse); |
| }); |
| |
| test('start setter throws exception when single assignment is true', () { |
| expect( |
| () { |
| final t = TimeRange()..start = Duration.zero; |
| t.start = Duration.zero; |
| }, |
| throwsAssertionError, |
| ); |
| }); |
| |
| test('start setter throws exception when value is after end', () { |
| expect( |
| () { |
| final t = TimeRange()..end = const Duration(seconds: 1); |
| t.start = const Duration(seconds: 2); |
| }, |
| throwsAssertionError, |
| ); |
| }); |
| |
| test('end setter throws exception when single assignment is true', () { |
| expect( |
| () { |
| final t = TimeRange()..end = Duration.zero; |
| t.end = Duration.zero; |
| }, |
| throwsAssertionError, |
| ); |
| }); |
| |
| test('end setter throws exception when value is before start', () { |
| expect( |
| () { |
| final t = TimeRange()..start = const Duration(seconds: 1); |
| t.end = Duration.zero; |
| }, |
| throwsAssertionError, |
| ); |
| }); |
| |
| test('isWellFormed', () { |
| expect( |
| (TimeRange() |
| ..start = Duration.zero |
| ..end = Duration.zero) |
| .isWellFormed, |
| isTrue, |
| ); |
| expect((TimeRange()..end = Duration.zero).isWellFormed, isFalse); |
| expect((TimeRange()..start = Duration.zero).isWellFormed, isFalse); |
| }); |
| |
| group('offset', () { |
| test('from well formed time range', () { |
| final t = TimeRange() |
| ..start = const Duration(milliseconds: 100) |
| ..end = const Duration(milliseconds: 200); |
| final offset = TimeRange.offset( |
| original: t, |
| offset: const Duration(milliseconds: 300), |
| ); |
| |
| expect(offset.start, equals(const Duration(milliseconds: 400))); |
| expect(offset.end, equals(const Duration(milliseconds: 500))); |
| }); |
| |
| test('from half formed time range', () { |
| var t = TimeRange()..start = const Duration(milliseconds: 100); |
| var offset = TimeRange.offset( |
| original: t, |
| offset: const Duration(milliseconds: 300), |
| ); |
| |
| expect(offset.start, equals(const Duration(milliseconds: 400))); |
| expect(offset.end, isNull); |
| |
| t = TimeRange()..end = const Duration(milliseconds: 200); |
| offset = TimeRange.offset( |
| original: t, |
| offset: const Duration(milliseconds: 300), |
| ); |
| |
| expect(offset.start, isNull); |
| expect(offset.end, equals(const Duration(milliseconds: 500))); |
| }); |
| |
| test('from empty time range', () { |
| final t = TimeRange(); |
| final offset = TimeRange.offset( |
| original: t, |
| offset: const Duration(milliseconds: 300), |
| ); |
| |
| expect(offset.start, isNull); |
| expect(offset.end, isNull); |
| }); |
| }); |
| }); |
| |
| test('formatDateTime', () { |
| expect(formatDateTime(DateTime(2020, 1, 16, 13)), '13:00:00.000'); |
| }); |
| |
| test('longestFittingSubstring', () { |
| const asciiStr = 'ComponentElement.performRebuild'; |
| const nonAsciiStr = 'ԪElement.updateChildԪ'; |
| num slowMeasureCallback(_) => 100; |
| |
| expect( |
| longestFittingSubstring( |
| asciiStr, |
| 0, |
| asciiMeasurements, |
| slowMeasureCallback, |
| ), |
| equals(''), |
| ); |
| expect( |
| longestFittingSubstring( |
| asciiStr, |
| 50, |
| asciiMeasurements, |
| slowMeasureCallback, |
| ), |
| equals('Compo'), |
| ); |
| expect( |
| longestFittingSubstring( |
| asciiStr, |
| 224, |
| asciiMeasurements, |
| slowMeasureCallback, |
| ), |
| equals('ComponentElement.performRebuild'), |
| ); |
| expect( |
| longestFittingSubstring( |
| asciiStr, |
| 300, |
| asciiMeasurements, |
| slowMeasureCallback, |
| ), |
| equals('ComponentElement.performRebuild'), |
| ); |
| |
| expect(nonAsciiStr.codeUnitAt(0), greaterThanOrEqualTo(128)); |
| expect( |
| longestFittingSubstring( |
| nonAsciiStr, |
| 99, |
| asciiMeasurements, |
| slowMeasureCallback, |
| ), |
| equals(''), |
| ); |
| expect( |
| longestFittingSubstring( |
| nonAsciiStr, |
| 100, |
| asciiMeasurements, |
| slowMeasureCallback, |
| ), |
| equals('Ԫ'), |
| ); |
| expect( |
| longestFittingSubstring( |
| nonAsciiStr, |
| 230, |
| asciiMeasurements, |
| slowMeasureCallback, |
| ), |
| equals('ԪElement.updateChild'), |
| ); |
| expect( |
| longestFittingSubstring( |
| nonAsciiStr, |
| 329, |
| asciiMeasurements, |
| slowMeasureCallback, |
| ), |
| equals('ԪElement.updateChild'), |
| ); |
| expect( |
| longestFittingSubstring( |
| nonAsciiStr, |
| 330, |
| asciiMeasurements, |
| slowMeasureCallback, |
| ), |
| equals('ԪElement.updateChildԪ'), |
| ); |
| }); |
| |
| test('isLetter', () { |
| expect(isLetter('@'.codeUnitAt(0)), isFalse); |
| expect(isLetter('['.codeUnitAt(0)), isFalse); |
| expect(isLetter('`'.codeUnitAt(0)), isFalse); |
| expect(isLetter('{'.codeUnitAt(0)), isFalse); |
| expect(isLetter('A'.codeUnitAt(0)), isTrue); |
| expect(isLetter('Z'.codeUnitAt(0)), isTrue); |
| expect(isLetter('a'.codeUnitAt(0)), isTrue); |
| expect(isLetter('z'.codeUnitAt(0)), isTrue); |
| }); |
| |
| test('getSimpleStackFrameName', () { |
| String name = |
| '_WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&' |
| 'SchedulerBinding.handleBeginFrame'; |
| expect( |
| getSimpleStackFrameName(name), |
| equals('SchedulerBinding.handleBeginFrame'), |
| ); |
| |
| name = |
| '_WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&' |
| 'SchedulerBinding.handleBeginFrame.<anonymous closure>'; |
| expect( |
| getSimpleStackFrameName(name), |
| equals('SchedulerBinding.handleBeginFrame.<closure>'), |
| ); |
| |
| name = '__CompactLinkedHashSet&_HashFieldBase&_HashBase&_OperatorEquals' |
| 'AndHashCode&_SetMixin.toList'; |
| expect(getSimpleStackFrameName(name), equals('_SetMixin.toList')); |
| |
| name = 'ClassName&SuperClassName&\$BadClassName.method'; |
| expect(getSimpleStackFrameName(name), equals('\$BadClassName.method')); |
| |
| // Ampersand as C++ reference. |
| name = |
| 'dart::DartEntry::InvokeFunction(dart::Function const&, dart::Array ' |
| 'const&, dart::Array const&, unsigned long)'; |
| expect(getSimpleStackFrameName(name), equals(name)); |
| |
| name = |
| 'SkCanvas::drawTextBlob(SkTextBlob const*, float, float, SkPaint const&)'; |
| expect(getSimpleStackFrameName(name), equals(name)); |
| |
| // No leading class names. |
| name = '_CustomZone.run'; |
| expect(getSimpleStackFrameName(name), equals(name)); |
| }); |
| |
| test('devToolsQueryParams', () { |
| expect( |
| devToolsQueryParams('http://localhost:123/?key=value.json&key2=123'), |
| equals({ |
| 'key': 'value.json', |
| 'key2': '123', |
| }), |
| ); |
| expect( |
| devToolsQueryParams('http://localhost:123/#/?key=value.json&key2=123'), |
| equals({ |
| 'key': 'value.json', |
| 'key2': '123', |
| }), |
| ); |
| expect( |
| devToolsQueryParams( |
| 'http://localhost:9101/#/appsize?key=value.json&key2=123', |
| ), |
| equals({ |
| 'key': 'value.json', |
| 'key2': '123', |
| }), |
| ); |
| }); |
| |
| test('getServiceUriFromQueryString', () { |
| expect( |
| getServiceUriFromQueryString( |
| 'http://localhost:123/?uri=http://localhost:456', |
| ).toString(), |
| equals('http://localhost:456'), |
| ); |
| expect( |
| getServiceUriFromQueryString('http://localhost:123/?port=789') |
| .toString(), |
| equals('ws://localhost:789/ws'), |
| ); |
| expect( |
| getServiceUriFromQueryString( |
| 'http://localhost:123/?port=789&token=kjy78', |
| ).toString(), |
| equals('ws://localhost:789/kjy78/ws'), |
| ); |
| }); |
| |
| group('safeDivide', () { |
| test('divides a finite result correctly', () { |
| expect(safeDivide(2.0, 1.0), 2.0); |
| expect(safeDivide(2, -4), -0.5); |
| }); |
| |
| test('produces the safe value on nan division', () { |
| expect(safeDivide(double.nan, 1.0), 0.0); |
| expect(safeDivide(double.nan, 1.0, ifNotFinite: 50.0), 50.0); |
| expect(safeDivide(0.0, double.nan, ifNotFinite: -5.0), -5.0); |
| }); |
| |
| test('produces the safe value on infinite division', () { |
| expect(safeDivide(double.infinity, 1.0), 0.0); |
| expect( |
| safeDivide( |
| double.nan, |
| double.negativeInfinity, |
| ifNotFinite: 50.0, |
| ), |
| 50.0, |
| ); |
| }); |
| |
| test('produces the safe value on null division', () { |
| expect(safeDivide(null, 1.0), 0.0); |
| expect(safeDivide(1.0, null, ifNotFinite: 50.0), 50.0); |
| }); |
| |
| test('produces the safe value on division by zero', () { |
| expect(safeDivide(1.0, 0.0), 0.0); |
| expect(safeDivide(-50.0, 0.0, ifNotFinite: 10.0), 10.0); |
| }); |
| }); |
| |
| group('Reporter', () { |
| int called = 0; |
| late Reporter reporter; |
| void call() { |
| called++; |
| } |
| |
| setUp(() { |
| called = 0; |
| reporter = Reporter(); |
| }); |
| test('notifies listeners', () { |
| expect(reporter.hasListeners, false); |
| reporter.addListener(call); |
| expect(called, 0); |
| expect(reporter.hasListeners, true); |
| reporter.notify(); |
| expect(called, 1); |
| reporter.notify(); |
| reporter.notify(); |
| expect(called, 3); |
| reporter.removeListener(call); |
| expect(called, 3); |
| }); |
| |
| test('notifies multiple listeners', () { |
| reporter.addListener(() => called++); |
| reporter.addListener(() => called++); |
| reporter.addListener(() => called++); |
| reporter.notify(); |
| expect(called, 3); |
| // Note that because we passed in anonymous callbacks, there's no way |
| // to remove them. |
| }); |
| |
| test('deduplicates listeners', () { |
| reporter.addListener(call); |
| reporter.addListener(call); |
| reporter.notify(); |
| expect(called, 1); |
| reporter.removeListener(call); |
| reporter.notify(); |
| expect(called, 1); |
| }); |
| |
| test('safely removes multiple times', () { |
| reporter.removeListener(call); |
| reporter.addListener(call); |
| reporter.notify(); |
| expect(called, 1); |
| reporter.removeListener(call); |
| reporter.removeListener(call); |
| reporter.notify(); |
| expect(called, 1); |
| }); |
| }); |
| |
| group('ValueReporter', () { |
| int called = 0; |
| void call() { |
| called++; |
| } |
| |
| late ValueReporter<String?> reporter; |
| setUp(() { |
| reporter = ValueReporter(null); |
| }); |
| test('notifies listeners', () { |
| expect(reporter.hasListeners, false); |
| reporter.addListener(call); |
| expect(called, 0); |
| expect(reporter.hasListeners, true); |
| reporter.value = 'first call'; |
| expect(called, 1); |
| reporter.value = 'second call'; |
| reporter.value = 'third call'; |
| expect(called, 3); |
| reporter.removeListener(call); |
| reporter.value = 'fourth call'; |
| expect(called, 3); |
| }); |
| }); |
| |
| group('SafeAccess', () { |
| test('safeFirst', () { |
| final list = <int?>[]; |
| final Iterable<int?> iterable = list; |
| expect(list.safeFirst, isNull); |
| expect(iterable.safeFirst, isNull); |
| list.addAll([1, 2, 3]); |
| expect(list.safeFirst, equals(1)); |
| expect(iterable.safeFirst, equals(1)); |
| list.insert(0, null); |
| expect(list.safeFirst, isNull); |
| expect(iterable.safeFirst, isNull); |
| }); |
| |
| test('safeLast', () { |
| final list = <int?>[]; |
| expect(list.safeLast, isNull); |
| list.addAll([1, 2, 3]); |
| expect(list.safeLast, equals(3)); |
| list.add(null); |
| expect(list.safeLast, isNull); |
| }); |
| |
| test('safeGet', () { |
| final list = <int>[]; |
| expect(list.safeGet(0), isNull); |
| list.addAll([1, 2]); |
| expect(list.safeGet(0), equals(1)); |
| expect(list.safeGet(1), equals(2)); |
| expect(list.safeGet(-1), isNull); |
| }); |
| |
| test('safeRemoveLast', () { |
| final list = <int>[]; |
| expect(list.safeRemoveLast(), isNull); |
| list.addAll([1, 2]); |
| expect(list.safeRemoveLast(), 2); |
| expect(list.safeRemoveLast(), 1); |
| expect(list.safeRemoveLast(), isNull); |
| }); |
| }); |
| }); |
| |
| group('LogicalKeySetExtension', () { |
| testWidgets('meta non-mac', (WidgetTester tester) async { |
| final keySet = |
| LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.keyP); |
| expect(keySet.describeKeys(), 'Meta-P'); |
| }); |
| |
| testWidgets('meta mac', (WidgetTester tester) async { |
| final keySet = |
| LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.keyP); |
| expect(keySet.describeKeys(isMacOS: true), '⌘P'); |
| }); |
| |
| testWidgets('ctrl', (WidgetTester tester) async { |
| final keySet = |
| LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyP); |
| expect(keySet.describeKeys(), 'Control-P'); |
| }); |
| }); |
| |
| group('MovingAverage', () { |
| const simpleDataSet = [ |
| 100, |
| 200, |
| 300, |
| 500, |
| 1000, |
| 2000, |
| 3000, |
| 4000, |
| 10000, |
| 100000, |
| ]; |
| |
| /// Data only has spikes. |
| const memorySizeDataSet = [ |
| 190432640, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 201045160, |
| 198200392, |
| 200144872, |
| 210110632, |
| 234077984, |
| 229029504, |
| 229029544, |
| 231396416, |
| 240465152, |
| 303434344, // Spike @ [25] (clear) |
| 302925712, |
| 356093472, |
| 354292096, |
| 400654120, |
| 400538848, |
| 402336872, |
| 444325760, |
| 444933104, |
| 341888120, |
| 406070376, |
| 343798216, |
| 392421072, |
| 392441080, |
| 481891656, |
| 481447920, |
| 433271776, |
| 464727280, |
| 494727280, |
| 564727280, |
| 524727280, |
| 534727280, |
| 564727280, |
| 764727280, // Spike @ [48] |
| 964727280, // Spike @ [49] |
| 1064727280, // Spike @ [50] |
| 1464727280, // Spike @ [51] |
| 2264727280, // Spike @ [52] |
| 2500000000, // Spike @ [53] |
| ]; |
| |
| /// Data has 5 spikes and 3 dips. |
| const dipsSpikesDataSet = [ |
| 190432640, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 190443808, |
| 5500000, // Dips @ [12] |
| 5600000, |
| 7443808, |
| 9043808, |
| 11045160, |
| 49800392, // Spikes @ [17] |
| 60144872, |
| 210110632, // Spikes @ [19] |
| 234077984, |
| 229029504, |
| 229029544, |
| 194000000, |
| 80000000, // Dips @ [24] |
| 100000000, |
| 150000000, |
| 240465152, // Spike @ [27] |
| 303434344, |
| 302925712, |
| 356093472, |
| 354292096, |
| 400654120, |
| 400538848, |
| 402336872, |
| 444325760, |
| 444933104, |
| 341888120, |
| 406070376, |
| 343798216, |
| 392421072, |
| 392441080, |
| 481891656, |
| 3000000, // Dips @ [43] |
| 3100000, |
| 3200000, |
| 330000000, // Spike @ [46] |
| 330000000, |
| 330000000, |
| 340000000, |
| 340000000, |
| 340000000, |
| 964727280, |
| 1064727280, |
| 1464727280, |
| 2264727280, // Spike @ [52] |
| 2500000000, |
| ]; |
| |
| void checkNewItemsAddedToDataSet(MovingAverage mA) { |
| mA.add(1000000); |
| mA.add(2000000); |
| mA.add(3000000); |
| expect(mA.dataSet.length, lessThan(mA.averagePeriod)); |
| expect(mA.mean.toInt(), equals(470853)); |
| expect(mA.hasSpike(), isTrue); |
| expect(mA.isDipping(), isFalse); |
| } |
| |
| test('basic MA', () { |
| // Creation of MovingAverage statically. |
| final simpleMA = MovingAverage(newDataSet: simpleDataSet); |
| expect(simpleMA.dataSet.length, lessThan(simpleMA.averagePeriod)); |
| expect(simpleMA.mean.toInt(), equals(12110)); |
| checkNewItemsAddedToDataSet(simpleMA); |
| |
| simpleMA.clear(); |
| expect(simpleMA.mean, equals(0)); |
| |
| // Dynamically add data to MovingAverage data set. |
| for (int i = 0; i < simpleDataSet.length; i++) { |
| simpleMA.add(simpleDataSet[i]); |
| } |
| // Should be identical to static one from above. |
| expect(simpleMA.mean.toInt(), equals(12110)); |
| checkNewItemsAddedToDataSet(simpleMA); |
| }); |
| |
| test('normal static MA', () { |
| // Creation of MovingAverage statically. |
| final mA = MovingAverage(newDataSet: memorySizeDataSet); |
| // Mean only calculated on last averagePeriod entries (50 default). |
| expect(mA.mean.toInt(), equals(462271799)); |
| expect(mA.dataSet.length, equals(mA.averagePeriod)); |
| expect(mA.hasSpike(), isTrue); |
| expect(mA.isDipping(), isFalse); |
| |
| mA.clear(); |
| expect(mA.mean, equals(0)); |
| expect(mA.dataSet.length, equals(0)); |
| }); |
| |
| test('dynamic spikes MA', () { |
| final mA = MovingAverage(); |
| |
| // Dynamically add data to MovingAverage data set. |
| for (int i = 0; i < 20; i++) { |
| mA.add(memorySizeDataSet[i]); |
| expect(mA.hasSpike(), isFalse); |
| expect(mA.isDipping(), isFalse); |
| } |
| expect(mA.mean.toInt(), equals(192829540)); |
| |
| for (int i = 20; i < 50; i++) { |
| mA.add(memorySizeDataSet[i]); |
| switch (i) { |
| case 25: |
| expect(mA.hasSpike(), isTrue); |
| expect(mA.isDipping(), isFalse); |
| mA.clear(); |
| expect(mA.dataSet.length, 0); |
| break; |
| case 48: |
| case 49: |
| expect(mA.hasSpike(), isTrue); |
| expect(mA.isDipping(), isFalse); |
| break; |
| default: |
| expect(mA.dataSet.length, i < 25 ? i + 1 : i - 25); |
| expect(mA.hasSpike(), isFalse); |
| expect(mA.isDipping(), isFalse); |
| } |
| } |
| expect(mA.mean.toInt(), equals(469047851)); |
| |
| expect(mA.dataSet.length, 24); |
| |
| for (int i = 50; i < memorySizeDataSet.length; i++) { |
| mA.add(memorySizeDataSet[i]); |
| switch (i) { |
| case 50: |
| expect(mA.mean.toInt(), equals(492875028)); |
| expect(mA.hasSpike(), isTrue); |
| expect(mA.isDipping(), isFalse); |
| expect(mA.dataSet.length, equals(25)); |
| break; |
| case 51: |
| expect(mA.mean.toInt(), equals(530253961)); |
| expect(mA.hasSpike(), isTrue); |
| expect(mA.isDipping(), isFalse); |
| expect(mA.dataSet.length, equals(26)); |
| break; |
| case 52: |
| expect(mA.mean.toInt(), equals(594493714)); |
| expect(mA.hasSpike(), isTrue); |
| expect(mA.isDipping(), isFalse); |
| expect(mA.dataSet.length, equals(27)); |
| break; |
| case 53: |
| expect(mA.mean.toInt(), equals(662547510)); |
| expect(mA.hasSpike(), isTrue); |
| expect(mA.isDipping(), isFalse); |
| expect(mA.dataSet.length, equals(28)); |
| break; |
| default: |
| expect(false, isTrue); |
| } |
| } |
| |
| // dataSet was cleared on first spike @ item 25 so |
| // dataSet only has the remaining 28 entries. |
| expect(mA.dataSet.length, 28); |
| expect(mA.mean.toInt(), equals(662547510)); |
| |
| mA.clear(); |
| expect(mA.mean, equals(0)); |
| expect(mA.dataSet.length, equals(0)); |
| }); |
| |
| test('dips and spikes MA', () { |
| final mA = MovingAverage(); |
| |
| // Dynamically add data to MovingAverage data set. |
| for (int i = 0; i < memorySizeDataSet.length; i++) { |
| mA.add(dipsSpikesDataSet[i]); |
| switch (i) { |
| case 12: |
| case 24: |
| case 43: |
| expect(mA.hasSpike(), isFalse); |
| expect(mA.isDipping(), isTrue); |
| break; |
| case 17: |
| case 19: |
| case 27: |
| case 46: |
| case 52: |
| expect(mA.hasSpike(), isTrue); |
| expect(mA.isDipping(), isFalse); |
| break; |
| default: |
| expect(mA.hasSpike(), isFalse); |
| expect(mA.isDipping(), isFalse); |
| } |
| if (mA.hasSpike() || mA.isDipping()) { |
| mA.clear(); |
| expect(mA.dataSet.length, 0); |
| } |
| } |
| }); |
| |
| group('ListExtension', () { |
| test('joinWith generates correct list', () { |
| expect([1, 2, 3, 4].joinWith(0), equals([1, 0, 2, 0, 3, 0, 4])); |
| expect([1].joinWith(0), equals([1])); |
| expect(['a', 'b'].joinWith('z'), equals(['a', 'z', 'b'])); |
| }); |
| |
| test('containsWhere', () { |
| final list = [1, 2, 1, 2, 3, 4]; |
| expect(list.containsWhere((element) => element == 1), isTrue); |
| expect(list.containsWhere((element) => element == 5), isFalse); |
| expect(list.containsWhere((element) => element + 2 == 3), isTrue); |
| |
| final otherList = ['hi', 'hey', 'foo', 'bar']; |
| expect( |
| otherList.containsWhere((element) => element.contains('h')), |
| isTrue, |
| ); |
| expect( |
| otherList.containsWhere((element) => element.startsWith('ba')), |
| isTrue, |
| ); |
| expect( |
| otherList.containsWhere((element) => element.endsWith('ba')), |
| isFalse, |
| ); |
| }); |
| }); |
| |
| group('SetExtension', () { |
| test('containsWhere', () { |
| final set = {1, 2, 3, 4}; |
| expect(set.containsWhere((element) => element == 1), isTrue); |
| expect(set.containsWhere((element) => element == 5), isFalse); |
| expect(set.containsWhere((element) => element + 2 == 3), isTrue); |
| |
| final otherSet = {'hi', 'hey', 'foo', 'bar'}; |
| expect( |
| otherSet.containsWhere((element) => element.contains('h')), |
| isTrue, |
| ); |
| expect( |
| otherSet.containsWhere((element) => element.startsWith('ba')), |
| isTrue, |
| ); |
| expect( |
| otherSet.containsWhere((element) => element.endsWith('ba')), |
| isFalse, |
| ); |
| }); |
| }); |
| |
| group('NullableStringExtension', () { |
| test('isNullOrEmpty', () { |
| String? str; |
| expect(str.isNullOrEmpty, isTrue); |
| str = ''; |
| expect(str.isNullOrEmpty, isTrue); |
| str = 'hello'; |
| expect(str.isNullOrEmpty, isFalse); |
| str = null; |
| expect(str.isNullOrEmpty, isTrue); |
| }); |
| }); |
| |
| group('StringExtension', () { |
| test('fuzzyMatch', () { |
| const str = 'hello_world_file'; |
| expect(str.caseInsensitiveFuzzyMatch('h'), isTrue); |
| expect(str.caseInsensitiveFuzzyMatch('o_'), isTrue); |
| expect(str.caseInsensitiveFuzzyMatch('hw'), isTrue); |
| expect(str.caseInsensitiveFuzzyMatch('hwf'), isTrue); |
| expect(str.caseInsensitiveFuzzyMatch('_e'), isTrue); |
| expect(str.caseInsensitiveFuzzyMatch('HWF'), isTrue); |
| expect(str.caseInsensitiveFuzzyMatch('_E'), isTrue); |
| |
| expect(str.caseInsensitiveFuzzyMatch('hwfh'), isFalse); |
| expect(str.caseInsensitiveFuzzyMatch('hfw'), isFalse); |
| expect(str.caseInsensitiveFuzzyMatch('gello'), isFalse); |
| expect(str.caseInsensitiveFuzzyMatch('files'), isFalse); |
| }); |
| |
| test('caseInsensitiveContains', () { |
| const str = 'This is a test string with a path/to/uri'; |
| expect(str.caseInsensitiveContains('test'), isTrue); |
| expect(str.caseInsensitiveContains('with a PATH/'), isTrue); |
| expect(str.caseInsensitiveContains('THIS IS A'), isTrue); |
| expect(str.caseInsensitiveContains('not a match'), isFalse); |
| expect(str.caseInsensitiveContains('test bool'), isFalse); |
| expect( |
| str.caseInsensitiveContains(RegExp('is.*path', caseSensitive: false)), |
| isTrue, |
| ); |
| expect( |
| () => str.caseInsensitiveContains(RegExp('is.*path')), |
| throwsAssertionError, |
| ); |
| expect( |
| str.caseInsensitiveContains( |
| RegExp('THIS IS.*TO/uri', caseSensitive: false), |
| ), |
| isTrue, |
| ); |
| expect( |
| str.caseInsensitiveContains( |
| RegExp('this.*does not match', caseSensitive: false), |
| ), |
| isFalse, |
| ); |
| }); |
| |
| test('caseInsensitiveEquals', () { |
| const str = 'hello, world!'; |
| expect(str.caseInsensitiveEquals(str), isTrue); |
| expect(str.caseInsensitiveEquals('HELLO, WORLD!'), isTrue); |
| expect(str.caseInsensitiveEquals('hElLo, WoRlD!'), isTrue); |
| expect(str.caseInsensitiveEquals('hello'), isFalse); |
| expect(str.caseInsensitiveEquals(''), isFalse); |
| expect(str.caseInsensitiveEquals(null), isFalse); |
| expect(''.caseInsensitiveEquals(''), isTrue); |
| expect(''.caseInsensitiveEquals(null), isFalse); |
| }); |
| |
| test('caseInsensitiveAllMatches', () { |
| const str = 'This is a TEST. Test string is "test"'; |
| final matches = 'test'.caseInsensitiveAllMatches(str).toList(); |
| expect(matches.length, equals(3)); |
| |
| // First match: 'TEST' |
| expect(matches[0].start, equals(10)); |
| expect(matches[0].end, equals(14)); |
| |
| // Second match: 'Test' |
| expect(matches[1].start, equals(16)); |
| expect(matches[1].end, equals(20)); |
| |
| // Third match: 'test' |
| expect(matches[2].start, equals(32)); |
| expect(matches[2].end, equals(36)); |
| |
| // Dart's allMatches returns 1 char matches when pattern is an empty string |
| expect( |
| ''.caseInsensitiveAllMatches('hello world').length, |
| equals('hello world'.length + 1), |
| ); |
| expect('*'.caseInsensitiveAllMatches('hello world'), isEmpty); |
| expect('test'.caseInsensitiveAllMatches(''), isEmpty); |
| expect('test'.caseInsensitiveAllMatches(null), isEmpty); |
| }); |
| }); |
| |
| group('BoolExtension', () { |
| test('boolCompare', () { |
| expect(true.boolCompare(true), equals(0)); |
| expect(false.boolCompare(false), equals(0)); |
| expect(true.boolCompare(false), equals(-1)); |
| expect(false.boolCompare(true), equals(1)); |
| }); |
| }); |
| |
| group('ProvidedControllerMixin', () { |
| setUp(() { |
| setGlobal(IdeTheme, IdeTheme()); |
| }); |
| |
| testWidgets( |
| 'updates controller when provided controller changes', |
| (WidgetTester tester) async { |
| final controller1 = TestProvidedController('id_1'); |
| final controller2 = TestProvidedController('id_2'); |
| final controllerNotifier = |
| ValueNotifier<TestProvidedController>(controller1); |
| |
| final provider = ValueListenableBuilder<TestProvidedController>( |
| valueListenable: controllerNotifier, |
| builder: (context, controller, _) { |
| return Provider<TestProvidedController>.value( |
| value: controller, |
| child: Builder( |
| builder: (context) { |
| return wrapSimple( |
| const TestStatefulWidget(), |
| ); |
| }, |
| ), |
| ); |
| }, |
| ); |
| |
| await tester.pumpWidget(provider); |
| expect(find.text('Value 1'), findsOneWidget); |
| expect(find.text('Controller id_1'), findsOneWidget); |
| |
| controllerNotifier.value = controller2; |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Value 2'), findsOneWidget); |
| expect(find.text('Controller id_2'), findsOneWidget); |
| }, |
| ); |
| }); |
| |
| group('subtractMaps', () { |
| test('subtracts non-null maps', () { |
| final subtract = {1: 'subtract'}; |
| final from = {1: 1.0, 2: 2.0}; |
| _SubtractionResult? elementSubtractor({ |
| required String? subtract, |
| required double? from, |
| }) => |
| _SubtractionResult(subtract: subtract, from: from); |
| |
| final result = subtractMaps<int, double, String, _SubtractionResult>( |
| substract: subtract, |
| from: from, |
| subtractor: elementSubtractor, |
| ); |
| |
| expect(const SetEquality().equals(result.keys.toSet(), {1, 2}), true); |
| expect( |
| result[1], |
| equals(_SubtractionResult(subtract: 'subtract', from: 1.0)), |
| ); |
| expect( |
| result[2], |
| equals(_SubtractionResult(subtract: null, from: 2.0)), |
| ); |
| }); |
| |
| test('subtracts null', () { |
| final from = {1: 1.0}; |
| _SubtractionResult? elementSubtractor({ |
| required String? subtract, |
| required double? from, |
| }) => |
| _SubtractionResult(subtract: subtract, from: from); |
| |
| final result = subtractMaps<int, double, String, _SubtractionResult>( |
| substract: null, |
| from: from, |
| subtractor: elementSubtractor, |
| ); |
| |
| expect(const SetEquality().equals(result.keys.toSet(), {1}), true); |
| expect( |
| result[1], |
| equals(_SubtractionResult(subtract: null, from: 1.0)), |
| ); |
| }); |
| |
| test('subtracts from null', () { |
| final subtract = {1: 'subtract'}; |
| _SubtractionResult? elementSubtractor({ |
| required String? subtract, |
| required double? from, |
| }) => |
| _SubtractionResult(subtract: subtract, from: from); |
| |
| final result = subtractMaps<int, double, String, _SubtractionResult>( |
| substract: subtract, |
| from: null, |
| subtractor: elementSubtractor, |
| ); |
| |
| expect(const SetEquality().equals(result.keys.toSet(), {1}), true); |
| expect( |
| result[1], |
| equals(_SubtractionResult(subtract: 'subtract', from: null)), |
| ); |
| }); |
| }); |
| }); |
| |
| group('joinWithTrailing', () { |
| test('joins no items', () { |
| expect([].joinWithTrailing(':'), equals('')); |
| }); |
| test(' joins 1 item', () { |
| expect(['A'].joinWithTrailing(':'), equals('A:')); |
| }); |
| test(' joins multiple items', () { |
| expect(['A', 'B', 'C'].joinWithTrailing(':'), equals('A:B:C:')); |
| }); |
| }); |
| } |
| |
| class _SubtractionResult { |
| _SubtractionResult({ |
| required this.subtract, |
| required this.from, |
| }); |
| final String? subtract; |
| final double? from; |
| |
| @override |
| bool operator ==(Object? other) { |
| if (other.runtimeType != runtimeType) { |
| return false; |
| } |
| |
| return other is _SubtractionResult && |
| other.subtract == subtract && |
| other.from == from; |
| } |
| |
| @override |
| int get hashCode => Object.hash(subtract, from); |
| |
| @override |
| String toString() => '$from - $subtract'; |
| } |
| |
| class TestProvidedController { |
| TestProvidedController(this.id); |
| |
| final String id; |
| } |
| |
| class TestStatefulWidget extends StatefulWidget { |
| const TestStatefulWidget({Key? key}) : super(key: key); |
| |
| @override |
| State<TestStatefulWidget> createState() => _TestStatefulWidgetState(); |
| } |
| |
| class _TestStatefulWidgetState extends State<TestStatefulWidget> |
| with ProvidedControllerMixin<TestProvidedController, TestStatefulWidget> { |
| int _value = 0; |
| |
| @override |
| void didChangeDependencies() { |
| super.didChangeDependencies(); |
| if (!initController()) return; |
| _value++; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Column( |
| children: [ |
| Text('Value $_value'), |
| Text('Controller ${controller.id}'), |
| ], |
| ); |
| } |
| } |
| |
| // This was generated from a canvas with font size 14.0. |
| const asciiMeasurements = [ |
| 0, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 0, |
| 3.8896484375, |
| 3.8896484375, |
| 3.8896484375, |
| 3.8896484375, |
| 3.8896484375, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 4.6619873046875, |
| 0, |
| 4.6619873046875, |
| 4.6619873046875, |
| 3.8896484375, |
| 3.8896484375, |
| 4.9697265625, |
| 7.7861328125, |
| 7.7861328125, |
| 12.4482421875, |
| 9.337890625, |
| 2.6728515625, |
| 4.662109375, |
| 4.662109375, |
| 5.4482421875, |
| 8.17578125, |
| 3.8896484375, |
| 4.662109375, |
| 3.8896484375, |
| 3.8896484375, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 3.8896484375, |
| 3.8896484375, |
| 8.17578125, |
| 8.17578125, |
| 8.17578125, |
| 7.7861328125, |
| 14.2119140625, |
| 9.337890625, |
| 9.337890625, |
| 10.1103515625, |
| 10.1103515625, |
| 9.337890625, |
| 8.5517578125, |
| 10.8896484375, |
| 10.1103515625, |
| 3.8896484375, |
| 7, |
| 9.337890625, |
| 7.7861328125, |
| 11.662109375, |
| 10.1103515625, |
| 10.8896484375, |
| 9.337890625, |
| 10.8896484375, |
| 10.1103515625, |
| 9.337890625, |
| 8.5517578125, |
| 10.1103515625, |
| 9.337890625, |
| 13.2138671875, |
| 9.337890625, |
| 9.337890625, |
| 8.5517578125, |
| 3.8896484375, |
| 3.8896484375, |
| 3.8896484375, |
| 6.5693359375, |
| 7.7861328125, |
| 4.662109375, |
| 7.7861328125, |
| 7.7861328125, |
| 7, |
| 7.7861328125, |
| 7.7861328125, |
| 3.8896484375, |
| 7.7861328125, |
| 7.7861328125, |
| 3.1103515625, |
| 3.1103515625, |
| 7, |
| 3.1103515625, |
| 11.662109375, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 7.7861328125, |
| 4.662109375, |
| 7, |
| 3.8896484375, |
| 7.7861328125, |
| 7, |
| 10.1103515625, |
| 7, |
| 7, |
| 7, |
| 4.67578125, |
| 3.63671875, |
| 4.67578125, |
| 8.17578125, |
| 0, |
| ]; |