diff --git a/app_dart/Dockerfile b/app_dart/Dockerfile index d2af87ee4..27652a3d4 100644 --- a/app_dart/Dockerfile +++ b/app_dart/Dockerfile @@ -4,7 +4,7 @@ # Dart Docker official images can be found here: https://hub.docker.com/_/dart -FROM dart:beta@sha256:b81805d66a633f8ee5bb20db229d7a680dd6ef16d1ef67fd74ca8a056f29a42f +FROM dart:beta@sha256:88ced76ff4a63e565872df26fe2442f060e3ecf828a272090ad10c79e9d044af WORKDIR /app diff --git a/app_dart/pubspec.yaml b/app_dart/pubspec.yaml index cbccc860b..c6f9fc746 100644 --- a/app_dart/pubspec.yaml +++ b/app_dart/pubspec.yaml @@ -45,7 +45,7 @@ dev_dependencies: analyzer: 5.13.0 build_runner: 2.4.6 fake_async: 1.3.1 - flutter_lints: 3.0.0 + flutter_lints: 3.0.1 json_serializable: 6.7.1 mockito: 5.4.2 platform: 3.1.3 diff --git a/auto_submit/Dockerfile b/auto_submit/Dockerfile index d2af87ee4..27652a3d4 100644 --- a/auto_submit/Dockerfile +++ b/auto_submit/Dockerfile @@ -4,7 +4,7 @@ # Dart Docker official images can be found here: https://hub.docker.com/_/dart -FROM dart:beta@sha256:b81805d66a633f8ee5bb20db229d7a680dd6ef16d1ef67fd74ca8a056f29a42f +FROM dart:beta@sha256:88ced76ff4a63e565872df26fe2442f060e3ecf828a272090ad10c79e9d044af WORKDIR /app diff --git a/auto_submit/pubspec.yaml b/auto_submit/pubspec.yaml index b074b7029..324b13823 100644 --- a/auto_submit/pubspec.yaml +++ b/auto_submit/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: dev_dependencies: build_runner: 2.4.6 json_serializable: 6.7.1 - flutter_lints: 3.0.0 + flutter_lints: 3.0.1 mockito: 5.4.2 test: 1.24.9 diff --git a/cipd_packages/codesign/pubspec.yaml b/cipd_packages/codesign/pubspec.yaml index 57d6ed3db..595409128 100644 --- a/cipd_packages/codesign/pubspec.yaml +++ b/cipd_packages/codesign/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: crypto: 3.0.3 fake_async: 1.3.1 file: 7.0.0 - flutter_lints: 3.0.0 + flutter_lints: 3.0.1 logging: 1.2.0 meta: 1.11.0 platform: 3.1.3 diff --git a/dashboard/android/app/build.gradle b/dashboard/android/app/build.gradle index 54ee204c1..fa1fb4d82 100644 --- a/dashboard/android/app/build.gradle +++ b/dashboard/android/app/build.gradle @@ -23,7 +23,7 @@ if (flutterVersionName == null) { } android { - namespace "com.example.dashboard" + namespace "com.appspot.flutter_dashboard.dashboard" compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion diff --git a/dashboard/android/app/src/main/AndroidManifest.xml b/dashboard/android/app/src/main/AndroidManifest.xml index 7932b44f3..c0d048a0e 100644 --- a/dashboard/android/app/src/main/AndroidManifest.xml +++ b/dashboard/android/app/src/main/AndroidManifest.xml @@ -2,12 +2,17 @@ + + + android:icon="@mipmap/ic_launcher" + android:banner="@mipmap/ic_launcher"> { /// Convert the fields from this class into a URL. /// /// This enables bookmarking state specific values, like [repo]. - void _updateNavigation(BuildContext context, BuildState buildState) { + void _updateNavigation(BuildContext context) { final Map queryParameters = {}; if (widget.queryParameters != null) { queryParameters.addAll(widget.queryParameters!); @@ -158,7 +159,7 @@ class BuildDashboardPageState extends State { child: const Text('Defaults'), ), TextButton( - onPressed: _filter == _settingsBasis ? null : () => _updateNavigation(context, buildState), + onPressed: _filter == _settingsBasis ? null : () => _updateNavigation(context), child: const Text('Apply'), ), TextButton( @@ -210,7 +211,7 @@ class BuildDashboardPageState extends State { ), onChanged: (String? selectedRepo) { repo = selectedRepo; - _updateNavigation(context, buildState); + _updateNavigation(context); }, items: buildState.repos.map>((String value) { return DropdownMenuItem( @@ -252,7 +253,7 @@ class BuildDashboardPageState extends State { ), onChanged: (String? selectedBranch) { branch = selectedBranch; - _updateNavigation(context, buildState); + _updateNavigation(context); }, items: [ DropdownMenuItem( @@ -297,7 +298,7 @@ class BuildDashboardPageState extends State { crossAxisAlignment: WrapCrossAlignment.center, children: [ const SizedBox(width: 10.0), - SizedBox.fromSize(size: const Size.square(TaskBox.cellSize), child: box), + SizedBox.fromSize(size: Size.square(TaskBox.of(context)), child: box), const SizedBox(width: 10.0), Text(description), ], @@ -322,8 +323,8 @@ class BuildDashboardPageState extends State { _getTaskKeyEntry( box: Center( child: Container( - width: TaskBox.cellSize * 0.8, - height: TaskBox.cellSize * 0.8, + width: TaskBox.of(context) * 0.8, + height: TaskBox.of(context) * 0.8, decoration: BoxDecoration( border: Border.all( width: 2.0, @@ -373,6 +374,12 @@ class BuildDashboardPageState extends State { } } + void _updatePage(BuildContext context, String newRepo, String newBranch) { + repo = newRepo; + branch = newBranch; + _updateNavigation(context); + } + @override Widget build(BuildContext context) { final bool isDark = Theme.of(context).brightness == Brightness.dark; @@ -392,57 +399,68 @@ class BuildDashboardPageState extends State { ); final BuildState buildState = Provider.of(context); buildState.updateCurrentRepoBranch(repo!, branch!); - return AnimatedBuilder( - animation: buildState, - builder: (BuildContext context, Widget? child) => Scaffold( - appBar: CocoonAppBar( - title: Tooltip( - message: _getStatusTitle(buildState), - child: Text( - _getStatusTitle(buildState), - ), - ), - backgroundColor: colorTable[buildState.isTreeBuilding], - actions: [ - if (!_smallScreen) ..._buildRepositorySelectionWidgets(context, buildState), - IconButton( - tooltip: 'Report Issue', - icon: const Icon(Icons.bug_report), - onPressed: () async { - if (await canLaunchUrl(flutterIssueUrl)) { - await launchUrl(flutterIssueUrl); - } else { - throw 'Could not launch $flutterIssueUrl'; - } - }, - ), - PopupMenuButton( - tooltip: 'Task Status Key', - child: const Icon(Icons.info_outline), - itemBuilder: (BuildContext context) => _getTaskKey(isDark), - ), - IconButton( - tooltip: 'Settings', - icon: const Icon(Icons.settings), - onPressed: _settingsBasis == null ? () => _showSettingsDialog() : null, - ), - ], - ), - body: ErrorBrookWatcher( - errors: buildState.errors, - child: Stack( - children: [ - SizedBox.expand( - child: TaskGridContainer( - filter: _filter, - useAnimatedLoading: true, + return CallbackShortcuts( + bindings: { + const SingleActivator(LogicalKeyboardKey.arrowUp): () => _updatePage(context, 'flutter', 'master'), + const SingleActivator(LogicalKeyboardKey.arrowDown): () => _updatePage(context, 'engine', 'main'), + const SingleActivator(LogicalKeyboardKey.arrowLeft): () => _updatePage(context, 'cocoon', 'main'), + const SingleActivator(LogicalKeyboardKey.arrowRight): () => _updatePage(context, 'packages', 'main'), + }, + child: Focus( + autofocus: true, + child: AnimatedBuilder( + animation: buildState, + builder: (BuildContext context, Widget? child) => Scaffold( + appBar: CocoonAppBar( + title: Tooltip( + message: _getStatusTitle(buildState), + child: Text( + _getStatusTitle(buildState), ), ), - if (_settingsBasis != null) _settingsDialog(context, buildState), - ], + backgroundColor: colorTable[buildState.isTreeBuilding], + actions: [ + if (!_smallScreen) ..._buildRepositorySelectionWidgets(context, buildState), + IconButton( + tooltip: 'Report Issue', + icon: const Icon(Icons.bug_report), + onPressed: () async { + if (await canLaunchUrl(flutterIssueUrl)) { + await launchUrl(flutterIssueUrl); + } else { + throw 'Could not launch $flutterIssueUrl'; + } + }, + ), + PopupMenuButton( + tooltip: 'Task Status Key', + child: const Icon(Icons.info_outline), + itemBuilder: (BuildContext context) => _getTaskKey(isDark), + ), + IconButton( + tooltip: 'Settings', + icon: const Icon(Icons.settings), + onPressed: _settingsBasis == null ? () => _showSettingsDialog() : null, + ), + ], + ), + body: ErrorBrookWatcher( + errors: buildState.errors, + child: Stack( + children: [ + SizedBox.expand( + child: TaskGridContainer( + filter: _filter, + useAnimatedLoading: true, + ), + ), + if (_settingsBasis != null) _settingsDialog(context, buildState), + ], + ), + ), + drawer: const DashboardNavigationDrawer(), ), ), - drawer: const DashboardNavigationDrawer(), ), ); } diff --git a/dashboard/lib/main.dart b/dashboard/lib/main.dart index fc8b843f3..1c2565424 100644 --- a/dashboard/lib/main.dart +++ b/dashboard/lib/main.dart @@ -7,6 +7,7 @@ import 'dart:io' if (kIsWeb) ''; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'build_dashboard_page.dart'; import 'service/cocoon.dart'; @@ -63,6 +64,10 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Build Dashboard — Cocoon', + shortcuts: { + ...WidgetsApp.defaultShortcuts, + const SingleActivator(LogicalKeyboardKey.select): const ActivateIntent(), + }, theme: ThemeData( useMaterial3: false, primaryTextTheme: const TextTheme( diff --git a/dashboard/lib/service/appengine_cocoon.dart b/dashboard/lib/service/appengine_cocoon.dart index cb7f53303..5c2dd51b5 100644 --- a/dashboard/lib/service/appengine_cocoon.dart +++ b/dashboard/lib/service/appengine_cocoon.dart @@ -6,7 +6,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:fixnum/fixnum.dart'; -import 'package:flutter/foundation.dart' show compute, kIsWeb, visibleForTesting; +import 'package:flutter/foundation.dart' show kIsWeb, visibleForTesting; import 'package:flutter_dashboard/model/branch.pb.dart'; import 'package:http/http.dart' as http; @@ -60,7 +60,7 @@ class AppEngineCocoonService implements CocoonService { try { final Map jsonResponse = jsonDecode(response.body); return CocoonResponse>.data( - await compute, List>(_commitStatusesFromJson, jsonResponse['Statuses']), + _commitStatusesFromJson(jsonResponse['Statuses']), ); } catch (error) { return CocoonResponse>.error(error.toString()); diff --git a/dashboard/lib/widgets/lattice.dart b/dashboard/lib/widgets/lattice.dart index 54626be49..3996bf5f9 100644 --- a/dashboard/lib/widgets/lattice.dart +++ b/dashboard/lib/widgets/lattice.dart @@ -9,6 +9,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'task_box.dart'; + typedef Painter = void Function(Canvas canvas, Rect rect); typedef LatticeTapCallback = void Function(Offset? offset); @@ -46,7 +48,6 @@ class LatticeScrollView extends StatelessWidget { this.verticalController, this.dragStartBehavior = DragStartBehavior.start, required this.cells, - required this.cellSize, }); final ScrollPhysics? horizontalPhysics; @@ -63,8 +64,6 @@ class LatticeScrollView extends StatelessWidget { final List> cells; - final Size cellSize; - @override Widget build(BuildContext context) { final TextDirection textDirection = this.textDirection ?? Directionality.of(context); @@ -96,7 +95,7 @@ class LatticeScrollView extends StatelessWidget { horizontalOffset: horizontalOffset, verticalOffset: verticalOffset, cells: cells, - cellSize: cellSize, + cellSize: Size.square(TaskBox.of(context)), ), ), ), diff --git a/dashboard/lib/widgets/task_box.dart b/dashboard/lib/widgets/task_box.dart index d468c9745..19dd62e23 100644 --- a/dashboard/lib/widgets/task_box.dart +++ b/dashboard/lib/widgets/task_box.dart @@ -2,13 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; typedef ShowSnackBarCallback = ScaffoldFeatureController Function(SnackBar snackBar); -class TaskBox { - /// How big to make each square in the grid. - static const double cellSize = 36; +class TaskBox extends StatelessWidget { + const TaskBox({super.key, this.cellSize, required this.child}); + + final Widget child; + final double? cellSize; + + static const double _kDefaultCellSize = 20; + static const double _kWebCellSize = 36; /// Status messages that map to [TaskStatus] enums. // TODO(chillers): Remove these and use TaskStatus enum when available. https://github.com/flutter/cocoon/issues/441 @@ -34,4 +40,37 @@ class TaskBox { statusInfraFailure: Colors.purple, statusInProgress: Colors.yellow, }; + + /// Returns the cell size of the nearest task box, or null if there is no + /// nearest task box. + static double? maybeOf(BuildContext context) { + final _TaskBox? box = context.dependOnInheritedWidgetOfExactType<_TaskBox>(); + return box?.size; + } + + /// Returns the cell size of the nearest task box. + static double of(BuildContext context) { + return maybeOf(context) ?? _kDefaultCellSize; + } + + @override + Widget build(BuildContext context) { + final double size = cellSize ?? (kIsWeb ? _kWebCellSize : _kDefaultCellSize); + return _TaskBox( + size: TaskBox.maybeOf(context) ?? size, + child: child, + ); + } +} + +class _TaskBox extends InheritedWidget { + const _TaskBox({ + required this.size, + required super.child, + }); + + final double size; + + @override + bool updateShouldNotify(covariant _TaskBox oldWidget) => size != oldWidget.size; } diff --git a/dashboard/lib/widgets/task_grid.dart b/dashboard/lib/widgets/task_grid.dart index b984df380..fe20c07fe 100644 --- a/dashboard/lib/widgets/task_grid.dart +++ b/dashboard/lib/widgets/task_grid.dart @@ -55,11 +55,13 @@ class TaskGridContainer extends StatelessWidget { ); } - return TaskGrid( - buildState: buildState, - commitStatuses: commitStatuses, - filter: filter, - useAnimatedLoading: useAnimatedLoading, + return TaskBox( + child: TaskGrid( + buildState: buildState, + commitStatuses: commitStatuses, + filter: filter, + useAnimatedLoading: useAnimatedLoading, + ), ); }, ); @@ -156,7 +158,6 @@ class _TaskGridState extends State { // TODO(ianh): Trigger the loading from the scroll offset, // rather than the current hack of loading during build. cells: _processCommitStatuses(widget), - cellSize: const Size.square(TaskBox.cellSize), verticalController: verticalController, horizontalController: horizontalController, ); @@ -307,10 +308,12 @@ class _TaskGridState extends State { WidgetBuilder? _builderFor(Task task) { if (task.attempts > 1 || task.isTestFlaky) { - return (BuildContext context) => const Padding( - padding: EdgeInsets.all(4.0), - child: Icon(Icons.priority_high), - ); + return (BuildContext context) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: Icon(Icons.priority_high, size: TaskBox.of(context) * 0.4), + ); + }; } return null; } @@ -318,11 +321,6 @@ class _TaskGridState extends State { static final List _loadingMessage = 'LOADING...'.runes.map((int codepoint) => String.fromCharCode(codepoint)).toList(); - static const TextStyle loadingStyle = TextStyle( - fontSize: TaskBox.cellSize * 0.9, - fontWeight: FontWeight.w900, - ); - List _generateLoadingRow(int length) { return [ LatticeCell( @@ -344,7 +342,10 @@ class _TaskGridState extends State { widget.buildState.fetchMoreCommitStatuses(); // This is safe to call many times. return Text( _loadingMessage[index % _loadingMessage.length], - style: loadingStyle, + style: TextStyle( + fontSize: TaskBox.of(context) * 0.9, + fontWeight: FontWeight.w900, + ), textAlign: TextAlign.center, ); }, diff --git a/dashboard/lib/widgets/task_icon.dart b/dashboard/lib/widgets/task_icon.dart index e5243b3d7..f8b5167fb 100644 --- a/dashboard/lib/widgets/task_icon.dart +++ b/dashboard/lib/widgets/task_icon.dart @@ -7,6 +7,8 @@ import 'package:url_launcher/url_launcher.dart'; import '../logic/qualified_task.dart'; +import 'task_box.dart'; + /// Header icon for all [Task]s that map to the same [Task.stageName] /// and [Task.name]. /// @@ -47,14 +49,20 @@ class TaskIcon extends StatelessWidget { } if (qualifiedTask.task!.toLowerCase().contains('_fuchsia')) { - return Image.asset( - 'assets/fuchsia.png', - color: blendFilter, + return Padding( + padding: const EdgeInsets.all(2.0), + child: Image.asset( + 'assets/fuchsia.png', + color: blendFilter, + ), ); } else if (qualifiedTask.task!.toLowerCase().contains('_web')) { - return Image.asset( - 'assets/chromium.png', - color: blendFilter, + return Padding( + padding: const EdgeInsets.all(2.0), + child: Image.asset( + 'assets/chromium.png', + color: blendFilter, + ), ); } else if (qualifiedTask.task!.toLowerCase().contains('_android')) { return Icon( @@ -62,9 +70,12 @@ class TaskIcon extends StatelessWidget { color: blendFilter, ); } else if (qualifiedTask.task!.toLowerCase().startsWith('linux')) { - return Image.asset( - 'assets/linux.png', - color: blendFilter, + return Padding( + padding: const EdgeInsets.all(2.0), + child: Image.asset( + 'assets/linux.png', + color: blendFilter, + ), ); } else if (qualifiedTask.task!.toLowerCase().startsWith('mac')) { if (qualifiedTask.task!.toLowerCase().contains('_ios')) { @@ -73,9 +84,12 @@ class TaskIcon extends StatelessWidget { color: blendFilter, ); } else { - return Image.asset( - 'assets/apple.png', - color: blendFilter, + return Padding( + padding: const EdgeInsets.all(2.0), + child: Image.asset( + 'assets/apple.png', + color: blendFilter, + ), ); } } else if (qualifiedTask.task!.toLowerCase().startsWith('win')) { @@ -96,15 +110,18 @@ class TaskIcon extends StatelessWidget { final Brightness brightness = Theme.of(context).brightness; final Widget icon = stageIconForBrightness(brightness); - return InkWell( - onTap: () { - launchUrl(Uri.parse(qualifiedTask.sourceConfigurationUrl)); - }, - child: Tooltip( - message: '${qualifiedTask.task} (${qualifiedTask.stage})', - child: Padding( - padding: const EdgeInsets.all(8.0), - child: icon, + return IconTheme.merge( + data: IconThemeData(size: TaskBox.of(context) - 5), + child: InkWell( + onTap: () { + launchUrl(Uri.parse(qualifiedTask.sourceConfigurationUrl)); + }, + child: Tooltip( + message: '${qualifiedTask.task} (${qualifiedTask.stage})', + child: Align( + alignment: Alignment.bottomCenter, + child: icon, + ), ), ), ); diff --git a/dashboard/lib/widgets/task_overlay.dart b/dashboard/lib/widgets/task_overlay.dart index 9d185ffda..8ba29193c 100644 --- a/dashboard/lib/widgets/task_overlay.dart +++ b/dashboard/lib/widgets/task_overlay.dart @@ -17,7 +17,9 @@ import 'progress_button.dart'; import 'task_box.dart'; class TaskOverlayEntryPositionDelegate extends SingleChildLayoutDelegate { - TaskOverlayEntryPositionDelegate(this.target); + TaskOverlayEntryPositionDelegate(this.target, {required this.cellSize}); + + final double cellSize; /// The offset of the target the tooltip is positioned near in the global /// coordinate system. @@ -26,10 +28,11 @@ class TaskOverlayEntryPositionDelegate extends SingleChildLayoutDelegate { static Offset positionDependentBox({ required Size size, required Size childSize, + required double cellSize, required Offset target, }) { const double margin = 10.0; - const double verticalOffset = TaskBox.cellSize * .9; + final double verticalOffset = cellSize * .9; // VERTICAL DIRECTION final bool fitsBelow = target.dy + verticalOffset + childSize.height <= size.height - margin; @@ -63,6 +66,7 @@ class TaskOverlayEntryPositionDelegate extends SingleChildLayoutDelegate { return positionDependentBox( size: size, childSize: childSize, + cellSize: cellSize, target: target, ); } @@ -120,8 +124,8 @@ class TaskOverlayEntry extends StatelessWidget { Positioned( top: position.dy, left: position.dx, - width: TaskBox.cellSize, - height: TaskBox.cellSize, + width: TaskBox.of(context), + height: TaskBox.of(context), child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.black, width: 4.0), @@ -138,7 +142,7 @@ class TaskOverlayEntry extends StatelessWidget { Positioned( // Move this overlay to be where the parent is child: CustomSingleChildLayout( - delegate: TaskOverlayEntryPositionDelegate(position), + delegate: TaskOverlayEntryPositionDelegate(position, cellSize: TaskBox.of(context)), child: TaskOverlayContents( showSnackBarCallback: showSnackBarCallback, buildState: buildState, diff --git a/dashboard/test/goldens/build_dashboard.defaultPropertySheet.dark.png b/dashboard/test/goldens/build_dashboard.defaultPropertySheet.dark.png index 3ee510439..899ee7b36 100644 Binary files a/dashboard/test/goldens/build_dashboard.defaultPropertySheet.dark.png and b/dashboard/test/goldens/build_dashboard.defaultPropertySheet.dark.png differ diff --git a/dashboard/test/goldens/build_dashboard.defaultPropertySheet.png b/dashboard/test/goldens/build_dashboard.defaultPropertySheet.png index 756c94a6a..a7d834ff5 100644 Binary files a/dashboard/test/goldens/build_dashboard.defaultPropertySheet.png and b/dashboard/test/goldens/build_dashboard.defaultPropertySheet.png differ diff --git a/dashboard/test/widgets/failures/commit_box_test.open_isolatedDiff.png b/dashboard/test/widgets/failures/commit_box_test.open_isolatedDiff.png deleted file mode 100644 index 5fb66b5c3..000000000 Binary files a/dashboard/test/widgets/failures/commit_box_test.open_isolatedDiff.png and /dev/null differ diff --git a/dashboard/test/widgets/failures/commit_box_test.open_maskedDiff.png b/dashboard/test/widgets/failures/commit_box_test.open_maskedDiff.png deleted file mode 100644 index 9b664df4f..000000000 Binary files a/dashboard/test/widgets/failures/commit_box_test.open_maskedDiff.png and /dev/null differ diff --git a/dashboard/test/widgets/failures/commit_box_test.open_masterImage.png b/dashboard/test/widgets/failures/commit_box_test.open_masterImage.png deleted file mode 100644 index 03aeed844..000000000 Binary files a/dashboard/test/widgets/failures/commit_box_test.open_masterImage.png and /dev/null differ diff --git a/dashboard/test/widgets/failures/commit_box_test.open_testImage.png b/dashboard/test/widgets/failures/commit_box_test.open_testImage.png deleted file mode 100644 index fb86eee11..000000000 Binary files a/dashboard/test/widgets/failures/commit_box_test.open_testImage.png and /dev/null differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.dev.mouse_scroll_x.png b/dashboard/test/widgets/goldens/task_grid_test.dev.mouse_scroll_x.png index 36a2da29c..171433ccb 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.dev.mouse_scroll_x.png and b/dashboard/test/widgets/goldens/task_grid_test.dev.mouse_scroll_x.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.dev.mouse_scroll_y.png b/dashboard/test/widgets/goldens/task_grid_test.dev.mouse_scroll_y.png index df8c41b31..9eb3ef1fa 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.dev.mouse_scroll_y.png and b/dashboard/test/widgets/goldens/task_grid_test.dev.mouse_scroll_y.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.dev.origin.dark.png b/dashboard/test/widgets/goldens/task_grid_test.dev.origin.dark.png index 4437f9039..c9873d9cd 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.dev.origin.dark.png and b/dashboard/test/widgets/goldens/task_grid_test.dev.origin.dark.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.dev.origin.png b/dashboard/test/widgets/goldens/task_grid_test.dev.origin.png index 2fc7f8879..81e623dbd 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.dev.origin.png and b/dashboard/test/widgets/goldens/task_grid_test.dev.origin.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_x.dark.png b/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_x.dark.png index 920cd5fba..f51f434dc 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_x.dark.png and b/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_x.dark.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_x.png b/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_x.png index 36a2da29c..171433ccb 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_x.png and b/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_x.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_y.dark.png b/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_y.dark.png index 40228cbb0..efeaa8a96 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_y.dark.png and b/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_y.dark.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_y.png b/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_y.png index df8c41b31..9eb3ef1fa 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_y.png and b/dashboard/test/widgets/goldens/task_grid_test.dev.scroll_y.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.differentTypes.png b/dashboard/test/widgets/goldens/task_grid_test.differentTypes.png index 461ad9b07..2e7ea769a 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.differentTypes.png and b/dashboard/test/widgets/goldens/task_grid_test.differentTypes.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.withL.png b/dashboard/test/widgets/goldens/task_grid_test.withL.png index f4277824f..88b151aaf 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.withL.png and b/dashboard/test/widgets/goldens/task_grid_test.withL.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.withSkips.png b/dashboard/test/widgets/goldens/task_grid_test.withSkips.png index 7679d703c..ca97804df 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.withSkips.png and b/dashboard/test/widgets/goldens/task_grid_test.withSkips.png differ diff --git a/dashboard/test/widgets/goldens/task_grid_test.withoutL.png b/dashboard/test/widgets/goldens/task_grid_test.withoutL.png index e31153c2c..f16856758 100644 Binary files a/dashboard/test/widgets/goldens/task_grid_test.withoutL.png and b/dashboard/test/widgets/goldens/task_grid_test.withoutL.png differ diff --git a/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_closed.png b/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_closed.png index 3bb71b3b1..074a979fe 100644 Binary files a/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_closed.png and b/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_closed.png differ diff --git a/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_open.png b/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_open.png index ade770d9b..15a6cec45 100644 Binary files a/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_open.png and b/dashboard/test/widgets/goldens/task_overlay_test.flaky_overlay_open.png differ diff --git a/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_closed.png b/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_closed.png index 3a9196650..51c93f564 100644 Binary files a/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_closed.png and b/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_closed.png differ diff --git a/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_open.png b/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_open.png index 216685786..5318313ed 100644 Binary files a/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_open.png and b/dashboard/test/widgets/goldens/task_overlay_test.nondevicelab_open.png differ diff --git a/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_closed.png b/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_closed.png index 4ddcb3350..b8bb5f176 100644 Binary files a/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_closed.png and b/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_closed.png differ diff --git a/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_open.png b/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_open.png index b613848c4..718a2da67 100644 Binary files a/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_open.png and b/dashboard/test/widgets/goldens/task_overlay_test.normal_overlay_open.png differ diff --git a/dashboard/test/widgets/task_grid_test.dart b/dashboard/test/widgets/task_grid_test.dart index 2c5e05e0f..44ce0ce3d 100644 --- a/dashboard/test/widgets/task_grid_test.dart +++ b/dashboard/test/widgets/task_grid_test.dart @@ -29,6 +29,8 @@ import '../utils/golden.dart'; import '../utils/mocks.dart'; import '../utils/task_icons.dart'; +const double _cellSize = 36; + void main() { setUp(() { FlutterAppIconsPlatform.instance = FakeFlutterAppIcons(); @@ -60,12 +62,15 @@ void main() { buildState.addListener(listener1); await tester.pumpWidget( - MaterialApp( - theme: ThemeData(useMaterial3: false), - home: ValueProvider( - value: buildState, - child: const Material( - child: TaskGridContainer(), + TaskBox( + cellSize: _cellSize, + child: MaterialApp( + theme: ThemeData(useMaterial3: false), + home: ValueProvider( + value: buildState, + child: const Material( + child: TaskGridContainer(), + ), ), ), ), @@ -115,12 +120,15 @@ void main() { buildState.addListener(listener1); await tester.pumpWidget( - MaterialApp( - theme: ThemeData(useMaterial3: false), - home: ValueProvider( - value: buildState, - child: const Material( - child: TaskGridContainer(), + TaskBox( + cellSize: _cellSize, + child: MaterialApp( + theme: ThemeData(useMaterial3: false), + home: ValueProvider( + value: buildState, + child: const Material( + child: TaskGridContainer(), + ), ), ), ), @@ -193,12 +201,15 @@ void main() { buildState.addListener(listener1); await tester.pumpWidget( - MaterialApp( - theme: ThemeData.dark(useMaterial3: false), - home: ValueProvider( - value: buildState, - child: const Material( - child: TaskGridContainer(), + TaskBox( + cellSize: _cellSize, + child: MaterialApp( + theme: ThemeData.dark(useMaterial3: false), + home: ValueProvider( + value: buildState, + child: const Material( + child: TaskGridContainer(), + ), ), ), ), @@ -246,12 +257,15 @@ void main() { buildState.addListener(listener1); await tester.pumpWidget( - MaterialApp( - theme: ThemeData.dark(), - home: ValueProvider( - value: buildState, - child: Material( - child: TaskGridContainer(filter: filter), + TaskBox( + cellSize: _cellSize, + child: MaterialApp( + theme: ThemeData.dark(), + home: ValueProvider( + value: buildState, + child: Material( + child: TaskGridContainer(filter: filter), + ), ), ), ), @@ -826,13 +840,16 @@ void main() { } Future expectTaskBoxColorWithMessage(WidgetTester tester, String message, Color expectedColor) async { + const double cellSize = 18; + const double cellPixelSize = cellSize * 3.0; + const double cellPixelArea = cellPixelSize * cellPixelSize; await tester.pumpWidget( MaterialApp( home: Material( child: Center( child: SizedBox( - height: TaskBox.cellSize * 3.0, - width: TaskBox.cellSize * 3.0, + height: cellPixelSize, + width: cellPixelSize, child: RepaintBoundary( child: TaskGrid( buildState: FakeBuildState( @@ -858,9 +875,8 @@ Future expectTaskBoxColorWithMessage(WidgetTester tester, String message, final ByteData? pixels = await tester.runAsync(() async { return (await renderObject!.toImage()).toByteData(); }); - assert(pixels!.lengthInBytes == ((TaskBox.cellSize * 3.0) * (TaskBox.cellSize * 3.0) * 4).round()); + expect(pixels!.lengthInBytes, (cellPixelArea * 4).round()); const double padding = 4.0; - final int rgba = pixels! - .getUint32(((((TaskBox.cellSize * 3.0) * (TaskBox.cellSize + padding)) + TaskBox.cellSize + padding).ceil()) * 4); + final int rgba = pixels.getUint32((((cellPixelSize * (cellSize + padding)) + cellSize + padding).ceil()) * 4); expect((rgba >> 8) | (rgba << 24) & 0xFFFFFFFF, expectedColor.value); } diff --git a/dashboard/test/widgets/task_overlay_test.dart b/dashboard/test/widgets/task_overlay_test.dart index e908b39fd..ba8f8a0c9 100644 --- a/dashboard/test/widgets/task_overlay_test.dart +++ b/dashboard/test/widgets/task_overlay_test.dart @@ -51,6 +51,8 @@ class TestGrid extends StatelessWidget { } } +const double _cellSize = 36; + void main() { final DateTime nowTime = DateTime.utc(2020, 9, 1, 12, 30); final DateTime createTime = nowTime.subtract(const Duration(minutes: 52)); @@ -87,12 +89,15 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - theme: ThemeData(useMaterial3: false), - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: expectedTask, + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: expectedTask, + ), ), ), ), @@ -106,7 +111,7 @@ void main() { await expectGoldenMatches(find.byType(MaterialApp), 'task_overlay_test.normal_overlay_closed.png'); - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); expect(find.text(expectedTask.name), findsOneWidget); @@ -116,7 +121,7 @@ void main() { // Since the overlay positions itself below the middle of the widget, // it is safe to click the widget to close it again. - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); expect(find.text(expectedTask.name), findsNothing); @@ -146,12 +151,15 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - theme: ThemeData(useMaterial3: false), - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: flakyTask, + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: flakyTask, + ), ), ), ), @@ -165,7 +173,7 @@ void main() { await expectGoldenMatches(find.byType(MaterialApp), 'task_overlay_test.flaky_overlay_closed.png'); - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); expect(find.text(flakyTask.name), findsOneWidget); @@ -196,11 +204,14 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: timeTask, + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: timeTask, + ), ), ), ), @@ -210,7 +221,7 @@ void main() { expect(find.text(timeTaskInfoString), findsNothing); // open the overlay to show the task summary - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); expect(find.text(timeTaskInfoString), findsOneWidget); @@ -237,11 +248,14 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: timeTask, + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: timeTask, + ), ), ), ), @@ -251,7 +265,7 @@ void main() { expect(find.text(timeTaskInfoString), findsNothing); // open the overlay to show the task summary - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); expect(find.text(timeTaskInfoString), findsOneWidget); @@ -275,11 +289,14 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: timeTask, + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: timeTask, + ), ), ), ), @@ -289,7 +306,7 @@ void main() { expect(find.text(timeTaskInfoString), findsNothing); // open the overlay to show the task summary - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); expect(find.text(timeTaskInfoString), findsOneWidget); @@ -300,14 +317,17 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - theme: ThemeData(useMaterial3: false), - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: Task() - ..stageName = 'luci' - ..status = TaskBox.statusSucceeded, + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: Task() + ..stageName = 'luci' + ..status = TaskBox.statusSucceeded, + ), ), ), ), @@ -317,7 +337,7 @@ void main() { await expectGoldenMatches(find.byType(MaterialApp), 'task_overlay_test.nondevicelab_closed.png'); - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); await expectGoldenMatches(find.byType(MaterialApp), 'task_overlay_test.nondevicelab_open.png'); @@ -327,14 +347,17 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: Task() - ..stageName = 'chromebot' - ..status = TaskBox.statusSucceeded - ..buildNumberList = '123', + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: Task() + ..stageName = 'chromebot' + ..status = TaskBox.statusSucceeded + ..buildNumberList = '123', + ), ), ), ), @@ -343,7 +366,7 @@ void main() { expect(find.byType(LuciTaskAttemptSummary), findsNothing); - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); expect(find.byType(LuciTaskAttemptSummary), findsOneWidget); @@ -353,14 +376,17 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: Task() - ..stageName = 'dart-internal' - ..status = TaskBox.statusSucceeded - ..buildNumberList = '123', + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: Task() + ..stageName = 'dart-internal' + ..status = TaskBox.statusSucceeded + ..buildNumberList = '123', + ), ), ), ), @@ -369,7 +395,7 @@ void main() { expect(find.byType(LuciTaskAttemptSummary), findsNothing); - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); expect(find.byType(LuciTaskAttemptSummary), findsOneWidget); @@ -389,11 +415,14 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: expectedTask, + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: expectedTask, + ), ), ), ), @@ -401,7 +430,7 @@ void main() { ); // Open the overlay - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); final ProgressButton? rerun = tester.element(find.text('RERUN')).findAncestorWidgetOfExactType(); @@ -424,11 +453,14 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - home: Scaffold( - body: TestGrid( - buildState: buildState, - task: expectedTask, + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + home: Scaffold( + body: TestGrid( + buildState: buildState, + task: expectedTask, + ), ), ), ), @@ -436,7 +468,7 @@ void main() { ); // Open the overlay - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); await tester.pump(); expect(find.text(TaskOverlayContents.rerunErrorMessage), findsNothing); @@ -473,15 +505,18 @@ void main() { await tester.pumpWidget( Now.fixed( dateTime: nowTime, - child: MaterialApp( - home: ValueProvider( - value: buildState, - child: Scaffold( - body: ErrorBrookWatcher( - errors: buildState.errors, - child: TestGrid( - buildState: buildState, - task: expectedTask, + child: TaskBox( + cellSize: _cellSize, + child: MaterialApp( + home: ValueProvider( + value: buildState, + child: Scaffold( + body: ErrorBrookWatcher( + errors: buildState.errors, + child: TestGrid( + buildState: buildState, + task: expectedTask, + ), ), ), ), @@ -490,7 +525,7 @@ void main() { ), ); - await tester.tapAt(const Offset(TaskBox.cellSize * 1.5, TaskBox.cellSize * 1.5)); + await tester.tapAt(const Offset(_cellSize * 1.5, _cellSize * 1.5)); // await tester.tap(find.byType(LatticeCell)); // await tester.tap(find.byType(TaskOverlayContents)); await tester.pump(); @@ -521,6 +556,7 @@ void main() { TaskOverlayEntryPositionDelegate.positionDependentBox( size: const Size(250, 150), childSize: childSize, + cellSize: _cellSize, target: const Offset(50.0, 50.0), ), const Offset(-25.0, 10.0), @@ -531,6 +567,7 @@ void main() { TaskOverlayEntryPositionDelegate.positionDependentBox( size: normalSize, childSize: childSize, + cellSize: _cellSize, target: const Offset(50.0, 50.0), ), const Offset(50.0, 82.4), @@ -540,6 +577,7 @@ void main() { TaskOverlayEntryPositionDelegate.positionDependentBox( size: normalSize, childSize: childSize, + cellSize: _cellSize, target: const Offset(590.0, 50.0), ), const Offset(490.0, 82.4), @@ -549,6 +587,7 @@ void main() { TaskOverlayEntryPositionDelegate.positionDependentBox( size: normalSize, childSize: childSize, + cellSize: _cellSize, target: const Offset(50.0, 500.0), ), const Offset(50.0, 320.0), @@ -558,6 +597,7 @@ void main() { TaskOverlayEntryPositionDelegate.positionDependentBox( size: normalSize, childSize: childSize, + cellSize: _cellSize, target: const Offset(590.0, 500.0), ), const Offset(490.0, 320.0),