| // Copyright 2019 The Flutter 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 'dart:typed_data'; | |
| import 'package:cocoon_common/rpc_model.dart'; | |
| import 'package:cocoon_common/task_status.dart'; | |
| import 'package:cocoon_integration_test/cocoon_integration_test.dart'; | |
| import 'package:flutter/gestures.dart'; | |
| import 'package:flutter/material.dart'; | |
| import 'package:flutter/rendering.dart'; | |
| import 'package:flutter_app_icons/flutter_app_icons_platform_interface.dart'; | |
| import 'package:flutter_dashboard/logic/task_grid_filter.dart'; | |
| import 'package:flutter_dashboard/service/integration_server_adapter.dart'; | |
| import 'package:flutter_dashboard/state/build.dart'; | |
| import 'package:flutter_dashboard/widgets/commit_box.dart'; | |
| import 'package:flutter_dashboard/widgets/lattice.dart'; | |
| import 'package:flutter_dashboard/widgets/state_provider.dart'; | |
| import 'package:flutter_dashboard/widgets/task_box.dart'; | |
| import 'package:flutter_dashboard/widgets/task_grid.dart'; | |
| import 'package:flutter_test/flutter_test.dart'; | |
| import '../utils/fake_build.dart'; | |
| import '../utils/fake_flutter_app_icons.dart'; | |
| import '../utils/generate_commit_for_tests.dart'; | |
| import '../utils/generate_task_for_tests.dart'; | |
| import '../utils/golden.dart'; | |
| import '../utils/mocks.dart'; | |
| import '../utils/task_icons.dart'; | |
| const double _cellSize = 36; | |
| void main() { | |
| setUp(() { | |
| FlutterAppIconsPlatform.instance = FakeFlutterAppIcons(); | |
| }); | |
| testWidgets( | |
| 'TaskGridContainer shows loading indicator when statuses is empty', | |
| (WidgetTester tester) async { | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| home: ValueProvider<BuildState>( | |
| value: FakeBuildState(), | |
| child: const Material(child: TaskGridContainer()), | |
| ), | |
| ), | |
| ); | |
| expect(find.byType(CircularProgressIndicator), findsOneWidget); | |
| expect(find.byType(LatticeScrollView), findsNothing); | |
| }, | |
| ); | |
| testWidgets('TaskGridContainer with IntegrationServerAdapter', ( | |
| WidgetTester tester, | |
| ) async { | |
| await precacheTaskIcons(tester); | |
| final service = IntegrationServerAdapter(IntegrationServer()); | |
| final buildState = BuildState( | |
| cocoonService: service, | |
| authService: MockFirebaseAuthService(), | |
| ); | |
| void listener1() {} | |
| buildState.addListener(listener1); | |
| await tester.pumpWidget( | |
| TaskBox( | |
| cellSize: _cellSize, | |
| child: MaterialApp( | |
| theme: ThemeData(useMaterial3: false), | |
| home: ValueProvider<BuildState>( | |
| value: buildState, | |
| child: const Material(child: TaskGridContainer()), | |
| ), | |
| ), | |
| ), | |
| ); | |
| await tester.pump(); | |
| final commitCount = tester.elementList(find.byType(CommitBox)).length; | |
| expect(commitCount, 16); // based on screen size this is how many show up | |
| final xPosition = tester.getTopLeft(find.byType(CommitBox).first).dx; | |
| for (var index = 0; index < commitCount; index += 1) { | |
| // All the x positions should match the first instance if they're all in the same column | |
| expect(tester.getTopLeft(find.byType(CommitBox).at(index)).dx, xPosition); | |
| } | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.dev.origin.png', | |
| ); | |
| // Check if the LOADING... indicator appears. | |
| service.paused = true; | |
| await tester.drag(find.byType(TaskGrid), const Offset(0.0, -5000.0)); | |
| await tester.pumpAndSettle(); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.dev.scroll_y.png', | |
| ); | |
| service.paused = false; | |
| await tester.pumpAndSettle(); | |
| // Check the right edge after the data comes in. | |
| service.paused = true; | |
| await tester.drag(find.byType(TaskGrid), const Offset(-5000.0, 0.0)); | |
| await tester.pumpAndSettle(); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.dev.scroll_x.png', | |
| ); | |
| service.paused = false; | |
| await tester.pumpAndSettle(); | |
| await tester.pumpWidget(Container()); | |
| buildState.dispose(); | |
| }); | |
| testWidgets('TaskGridContainer supports mouse drag', ( | |
| WidgetTester tester, | |
| ) async { | |
| await precacheTaskIcons(tester); | |
| final service = IntegrationServerAdapter(IntegrationServer()); | |
| final buildState = BuildState( | |
| cocoonService: service, | |
| authService: MockFirebaseAuthService(), | |
| ); | |
| void listener1() {} | |
| buildState.addListener(listener1); | |
| await tester.pumpWidget( | |
| TaskBox( | |
| cellSize: _cellSize, | |
| child: MaterialApp( | |
| theme: ThemeData(useMaterial3: false), | |
| home: ValueProvider<BuildState>( | |
| value: buildState, | |
| child: const Material(child: TaskGridContainer()), | |
| ), | |
| ), | |
| ), | |
| ); | |
| await tester.pump(); | |
| final commitCount = tester.elementList(find.byType(CommitBox)).length; | |
| expect(commitCount, 16); // based on screen size this is how many show up | |
| final xPosition = tester.getTopLeft(find.byType(CommitBox).first).dx; | |
| for (var index = 0; index < commitCount; index += 1) { | |
| // All the x positions should match the first instance if they're all in the same column | |
| expect(tester.getTopLeft(find.byType(CommitBox).at(index)).dx, xPosition); | |
| } | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.dev.origin.png', | |
| ); | |
| // Check if the LOADING... indicator appears. | |
| service.paused = true; | |
| var gesture = await tester.startGesture( | |
| tester.getCenter(find.byType(TaskGrid)), | |
| kind: PointerDeviceKind.mouse, | |
| ); | |
| for (var i = 0; i < 100; i += 1) { | |
| await gesture.moveBy(const Offset(0.0, -50.0)); | |
| } | |
| await gesture.up(); | |
| await tester.pumpAndSettle(); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.dev.mouse_scroll_y.png', | |
| ); | |
| await gesture.removePointer(); | |
| service.paused = false; | |
| await tester.pumpAndSettle(); | |
| // Check the right edge after the data comes in. | |
| service.paused = true; | |
| gesture = await tester.startGesture( | |
| tester.getCenter(find.byType(TaskGrid)), | |
| kind: PointerDeviceKind.mouse, | |
| ); | |
| for (var i = 0; i < 100; i += 1) { | |
| await gesture.moveBy(const Offset(-50.0, 0)); | |
| } | |
| //await gesture.moveBy(const Offset(-5000, 0)); | |
| await gesture.up(); | |
| await tester.pumpAndSettle(); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.dev.mouse_scroll_x.png', | |
| ); | |
| service.paused = false; | |
| await gesture.removePointer(); | |
| await tester.pumpWidget(Container()); | |
| buildState.dispose(); | |
| }); | |
| testWidgets('TaskGridContainer with IntegrationServerAdapter - dark', ( | |
| WidgetTester tester, | |
| ) async { | |
| await precacheTaskIcons(tester); | |
| final service = IntegrationServerAdapter(IntegrationServer()); | |
| final buildState = BuildState( | |
| cocoonService: service, | |
| authService: MockFirebaseAuthService(), | |
| ); | |
| void listener1() {} | |
| buildState.addListener(listener1); | |
| await tester.pumpWidget( | |
| TaskBox( | |
| cellSize: _cellSize, | |
| child: MaterialApp( | |
| theme: ThemeData.dark(useMaterial3: false), | |
| home: ValueProvider<BuildState>( | |
| value: buildState, | |
| child: const Material(child: TaskGridContainer()), | |
| ), | |
| ), | |
| ), | |
| ); | |
| await tester.pump(); | |
| final commitCount = tester.elementList(find.byType(CommitBox)).length; | |
| expect(commitCount, 16); // based on screen size this is how many show up | |
| final xPosition = tester.getTopLeft(find.byType(CommitBox).first).dx; | |
| for (var index = 0; index < commitCount; index += 1) { | |
| // All the x positions should match the first instance if they're all in the same column | |
| expect(tester.getTopLeft(find.byType(CommitBox).at(index)).dx, xPosition); | |
| } | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.dev.origin.dark.png', | |
| ); | |
| // Check if the LOADING... indicator appears. | |
| service.paused = true; | |
| await tester.drag(find.byType(TaskGrid), const Offset(0.0, -5000.0)); | |
| await tester.pumpAndSettle(); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.dev.scroll_y.dark.png', | |
| ); | |
| service.paused = false; | |
| await tester.pumpAndSettle(); | |
| // Check the right edge after the data comes in. | |
| service.paused = true; | |
| await tester.drag(find.byType(TaskGrid), const Offset(-5000.0, 0.0)); | |
| await tester.pumpAndSettle(); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.dev.scroll_x.dark.png', | |
| ); | |
| service.paused = false; | |
| await tester.pumpAndSettle(); | |
| await tester.pumpWidget(Container()); | |
| buildState.dispose(); | |
| }); | |
| Future<void> testGrid( | |
| WidgetTester tester, | |
| TaskGridFilter? filter, | |
| int rows, | |
| int cols, | |
| ) async { | |
| final buildState = BuildState( | |
| cocoonService: IntegrationServerAdapter(IntegrationServer()), | |
| authService: MockFirebaseAuthService(), | |
| ); | |
| void listener1() {} | |
| buildState.addListener(listener1); | |
| await tester.pumpWidget( | |
| TaskBox( | |
| cellSize: _cellSize, | |
| child: MaterialApp( | |
| theme: ThemeData.dark(), | |
| home: ValueProvider<BuildState>( | |
| value: buildState, | |
| child: Material(child: TaskGridContainer(filter: filter)), | |
| ), | |
| ), | |
| ), | |
| ); | |
| await tester.pump(); | |
| expect(find.byType(LatticeScrollView), findsOneWidget); | |
| final lattice = | |
| find.byType(LatticeScrollView).evaluate().first.widget | |
| as LatticeScrollView; | |
| expect(lattice.cells.length, rows); | |
| for (final row in lattice.cells) { | |
| expect(row.length, cols); | |
| } | |
| await tester.pumpWidget(Container()); | |
| buildState.dispose(); | |
| } | |
| testWidgets('Task name filter affects grid', (WidgetTester tester) async { | |
| // Default filters | |
| await testGrid(tester, null, 27, 101); | |
| await testGrid(tester, TaskGridFilter(), 27, 101); | |
| await testGrid(tester, TaskGridFilter.fromMap(null), 27, 101); | |
| await testGrid(tester, TaskGridFilter()..showBringup = false, 27, 100); | |
| // QualifiedTask (column) filters | |
| await testGrid( | |
| tester, | |
| TaskGridFilter()..taskFilter = RegExp('Linux_android 2'), | |
| 27, | |
| 12, | |
| ); | |
| // CommitStatus (row) filters | |
| await testGrid( | |
| tester, | |
| TaskGridFilter()..authorFilter = RegExp('yegor'), | |
| 4, | |
| 101, | |
| ); | |
| await testGrid( | |
| tester, | |
| TaskGridFilter()..messageFilter = RegExp('developer'), | |
| 18, | |
| 101, | |
| ); | |
| await testGrid( | |
| tester, | |
| TaskGridFilter() | |
| ..hashFilter = RegExp('fb75b2b671c7702b549a80a420144097f4fab5a9'), | |
| 2, // codefu: these are magic numbers and this test is bad. | |
| 101, | |
| ); | |
| }); | |
| testWidgets('Skipped tasks do not break the grid', ( | |
| WidgetTester tester, | |
| ) async { | |
| await precacheTaskIcons(tester); | |
| // Matrix Diagram: | |
| // | |
| // ✓☐☐ | |
| // ☐✓☐ | |
| // ☐☐✓ | |
| // | |
| // To construct the matrix from this diagram, each [CommitStatus] must have a unique [Task] | |
| // that does not share its name with any other [Task]. This will make that [CommitStatus] have | |
| // its task on its own unique row and column. | |
| final statusesWithSkips = [ | |
| CommitStatus( | |
| commit: generateCommitForTest(), | |
| tasks: [ | |
| generateTaskForTest(status: TaskStatus.succeeded, builderName: '1'), | |
| ], | |
| ), | |
| CommitStatus( | |
| commit: generateCommitForTest(), | |
| tasks: [ | |
| generateTaskForTest(status: TaskStatus.succeeded, builderName: '2'), | |
| ], | |
| ), | |
| CommitStatus( | |
| commit: generateCommitForTest(), | |
| tasks: [ | |
| generateTaskForTest(status: TaskStatus.succeeded, builderName: '3'), | |
| ], | |
| ), | |
| ]; | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| theme: ThemeData(useMaterial3: false), | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState(), | |
| commitStatuses: statusesWithSkips, | |
| ), | |
| ), | |
| ), | |
| ); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.withSkips.png', | |
| ); | |
| }); | |
| testWidgets('Cocoon and LUCI tasks share the same column', ( | |
| WidgetTester tester, | |
| ) async { | |
| await precacheTaskIcons(tester); | |
| // Matrix Diagram: | |
| // | |
| // ✓ | |
| // ✓ | |
| // | |
| // To construct the matrix from this diagram, each [CommitStatus] will have a [Task] | |
| // that shares its name, but will have a different stage name. | |
| final statuses = [ | |
| CommitStatus( | |
| commit: generateCommitForTest(), | |
| tasks: [ | |
| generateTaskForTest(status: TaskStatus.succeeded, builderName: '1'), | |
| ], | |
| ), | |
| CommitStatus( | |
| commit: generateCommitForTest(), | |
| tasks: [ | |
| generateTaskForTest(status: TaskStatus.succeeded, builderName: '1'), | |
| ], | |
| ), | |
| ]; | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState(), | |
| commitStatuses: statuses, | |
| ), | |
| ), | |
| ), | |
| ); | |
| expect(find.byType(LatticeScrollView), findsOneWidget); | |
| final lattice = | |
| find.byType(LatticeScrollView).evaluate().first.widget | |
| as LatticeScrollView; | |
| // Rows (task icon, two commits, load more row) | |
| expect(lattice.cells.length, 4); | |
| // Columns (commit box, task) | |
| expect(lattice.cells.first.length, 2); | |
| expect(lattice.cells[1].length, 2); | |
| }); | |
| testWidgets('TaskGrid honors moreStatusesExist', (WidgetTester tester) async { | |
| await precacheTaskIcons(tester); | |
| final commitStatuses = <CommitStatus>[ | |
| CommitStatus( | |
| commit: generateCommitForTest(), | |
| tasks: [ | |
| generateTaskForTest( | |
| status: TaskStatus.succeeded, | |
| builderName: 'Task Name', | |
| ), | |
| ], | |
| ), | |
| ]; | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| theme: ThemeData(useMaterial3: false), | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState(moreStatusesExist: false), | |
| commitStatuses: commitStatuses, | |
| ), | |
| ), | |
| ), | |
| ); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.withoutL.png', | |
| ); | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| theme: ThemeData(useMaterial3: false), | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState(moreStatusesExist: true), | |
| commitStatuses: commitStatuses, | |
| ), | |
| ), | |
| ), | |
| ); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.withL.png', | |
| ); | |
| }); | |
| testWidgets('TaskGrid shows icon for rerun tasks', ( | |
| WidgetTester tester, | |
| ) async { | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState( | |
| authService: MockFirebaseAuthService(), | |
| cocoonService: MockCocoonService(), | |
| ), | |
| commitStatuses: [ | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [ | |
| generateTaskForTest( | |
| status: TaskStatus.succeeded, | |
| attempts: 2, | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| expect(find.byIcon(Icons.priority_high), findsOneWidget); | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState( | |
| authService: MockFirebaseAuthService(), | |
| cocoonService: MockCocoonService(), | |
| ), | |
| commitStatuses: [ | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [ | |
| generateTaskForTest( | |
| status: TaskStatus.succeeded, | |
| attempts: 1, | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| expect(find.byIcon(Icons.priority_high), findsNothing); | |
| }); | |
| testWidgets('TaskGrid shows icon for isTestFlaky tasks', ( | |
| WidgetTester tester, | |
| ) async { | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState( | |
| authService: MockFirebaseAuthService(), | |
| cocoonService: MockCocoonService(), | |
| ), | |
| commitStatuses: [ | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [ | |
| generateTaskForTest( | |
| status: TaskStatus.succeeded, | |
| attempts: 2, | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| expect(find.byIcon(Icons.priority_high), findsOneWidget); | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState( | |
| authService: MockFirebaseAuthService(), | |
| cocoonService: MockCocoonService(), | |
| ), | |
| commitStatuses: [ | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [generateTaskForTest(status: TaskStatus.succeeded)], | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| expect(find.byIcon(Icons.priority_high), findsNothing); | |
| }); | |
| testWidgets( | |
| 'TaskGrid shows icon for isTestFlaky tasks with multiple attempts', | |
| (WidgetTester tester) async { | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState( | |
| authService: MockFirebaseAuthService(), | |
| cocoonService: MockCocoonService(), | |
| ), | |
| commitStatuses: [ | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [ | |
| generateTaskForTest( | |
| status: TaskStatus.succeeded, | |
| builderName: '1', | |
| attempts: 3, | |
| ), | |
| ], | |
| ), | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [ | |
| generateTaskForTest( | |
| status: TaskStatus.succeeded, | |
| builderName: '2', | |
| attempts: 1, | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| expect(find.byIcon(Icons.priority_high), findsNWidgets(1)); | |
| // check the order of the items. The flaky should be to the left and first. | |
| expect(find.byType(TaskGrid).first, findsAtLeastNWidgets(1)); | |
| final latticeScrollView = | |
| tester.firstWidget(find.byType(LatticeScrollView)) | |
| as LatticeScrollView; | |
| final cells = latticeScrollView.cells; | |
| final myCells = cells.first; | |
| expect(myCells.length, 3); | |
| myCells.removeAt(0); // the first element is the github author box. | |
| expect(myCells[0].taskName, '1'); | |
| expect(myCells[1].taskName, '2'); | |
| }, | |
| ); | |
| testWidgets('TaskGrid can handle all the various different statuses', ( | |
| WidgetTester tester, | |
| ) async { | |
| await precacheTaskIcons(tester); | |
| final statuses = [ | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [ | |
| for (final status in TaskBox.statusColor.keys) | |
| generateTaskForTest(status: status, builderName: 'task_$status'), | |
| ], | |
| ), | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [ | |
| for (final status in TaskBox.statusColor.keys) | |
| generateTaskForTest( | |
| status: status, | |
| builderName: 'task_attempts2_$status', | |
| attempts: 2, | |
| ), | |
| ], | |
| ), | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [ | |
| for (final status in TaskBox.statusColor.keys) | |
| generateTaskForTest( | |
| status: status, | |
| builderName: 'task_bringup_$status', | |
| bringup: true, | |
| ), | |
| ], | |
| ), | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Cast'), | |
| tasks: [ | |
| for (final status in TaskBox.statusColor.keys) | |
| generateTaskForTest( | |
| status: status, | |
| builderName: 'task_attempts2_bringup_$status', | |
| attempts: 2, | |
| bringup: true, | |
| ), | |
| ], | |
| ), | |
| ]; | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| theme: ThemeData(useMaterial3: false), | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState(), | |
| commitStatuses: statuses, | |
| filter: TaskGridFilter()..showBringup = true, | |
| ), | |
| ), | |
| ), | |
| ); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.filterShowBringup.differentTypes.png', | |
| ); | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| theme: ThemeData(useMaterial3: false), | |
| home: Material( | |
| child: TaskGrid( | |
| buildState: FakeBuildState(), | |
| commitStatuses: statuses, | |
| filter: TaskGridFilter(), | |
| ), | |
| ), | |
| ), | |
| ); | |
| await expectGoldenMatches( | |
| find.byType(TaskGrid), | |
| 'task_grid_test.filterDefault.differentTypes.png', | |
| ); | |
| }); | |
| // Table Driven Approach to ensure every message does show the corresponding color | |
| TaskBox.statusColor.forEach((status, color) { | |
| testWidgets('Is the color $color when given the status $status', ( | |
| WidgetTester tester, | |
| ) async { | |
| await _expectTaskBoxColorWithStatus(tester, status, color); | |
| }); | |
| }); | |
| } | |
| Future<void> _expectTaskBoxColorWithStatus( | |
| WidgetTester tester, | |
| TaskStatus status, | |
| Color expectedColor, | |
| ) async { | |
| const double cellSize = 18; | |
| const cellPixelSize = cellSize * 3.0; | |
| const cellPixelArea = cellPixelSize * cellPixelSize; | |
| await tester.pumpWidget( | |
| MaterialApp( | |
| home: Material( | |
| child: Center( | |
| child: SizedBox( | |
| height: cellPixelSize, | |
| width: cellPixelSize, | |
| child: RepaintBoundary( | |
| child: TaskGrid( | |
| buildState: FakeBuildState( | |
| authService: MockFirebaseAuthService(), | |
| cocoonService: MockCocoonService(), | |
| ), | |
| commitStatuses: <CommitStatus>[ | |
| CommitStatus( | |
| commit: generateCommitForTest(author: 'Mathilda'), | |
| tasks: [generateTaskForTest(status: status)], | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ); | |
| final renderObject = | |
| tester.renderObject(find.byType(TaskGrid)).parent | |
| as RenderRepaintBoundary?; | |
| final pixels = await tester.runAsync<ByteData?>(() async { | |
| return (await renderObject!.toImage()).toByteData(); | |
| }); | |
| expect(pixels!.lengthInBytes, (cellPixelArea * 4).round()); | |
| const padding = 4.0; | |
| final rgba = pixels.getUint32( | |
| (((cellPixelSize * (cellSize + padding)) + cellSize + padding).ceil()) * 4, | |
| ); | |
| final actualColor = Color((rgba >> 8) | (rgba << 24) & 0xFFFFFFFF); | |
| expect(actualColor, isSameColorAs(expectedColor)); | |
| } |