| // 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: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/qualified_task.dart'; |
| import 'package:flutter_dashboard/logic/task_grid_filter.dart'; |
| import 'package:flutter_dashboard/model/commit.pb.dart'; |
| import 'package:flutter_dashboard/model/commit_status.pb.dart'; |
| import 'package:flutter_dashboard/model/task.pb.dart'; |
| import 'package:flutter_dashboard/service/dev_cocoon.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_dashboard/widgets/task_icon.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../utils/fake_build.dart'; |
| import '../utils/fake_flutter_app_icons.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 DevelopmentCocoonService', (WidgetTester tester) async { |
| await precacheTaskIcons(tester); |
| final DevelopmentCocoonService service = DevelopmentCocoonService(DateTime.utc(2020)); |
| final BuildState buildState = BuildState( |
| cocoonService: service, |
| authService: MockGoogleSignInService(), |
| ); |
| 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 int commitCount = tester.elementList(find.byType(CommitBox)).length; |
| expect(commitCount, 16); // based on screen size this is how many show up |
| |
| final double xPosition = tester.getTopLeft(find.byType(CommitBox).first).dx; |
| |
| for (int 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 DevelopmentCocoonService service = DevelopmentCocoonService(DateTime.utc(2020)); |
| final BuildState buildState = BuildState( |
| cocoonService: service, |
| authService: MockGoogleSignInService(), |
| ); |
| 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 int commitCount = tester.elementList(find.byType(CommitBox)).length; |
| expect(commitCount, 16); // based on screen size this is how many show up |
| |
| final double xPosition = tester.getTopLeft(find.byType(CommitBox).first).dx; |
| |
| for (int 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; |
| |
| TestGesture gesture = await tester.startGesture( |
| tester.getCenter(find.byType(TaskGrid)), |
| kind: PointerDeviceKind.mouse, |
| ); |
| for (int 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 (int 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 DevelopmentCocoonService - dark', (WidgetTester tester) async { |
| await precacheTaskIcons(tester); |
| final DevelopmentCocoonService service = DevelopmentCocoonService(DateTime.utc(2020)); |
| final BuildState buildState = BuildState( |
| cocoonService: service, |
| authService: MockGoogleSignInService(), |
| ); |
| 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 int commitCount = tester.elementList(find.byType(CommitBox)).length; |
| expect(commitCount, 16); // based on screen size this is how many show up |
| |
| final double xPosition = tester.getTopLeft(find.byType(CommitBox).first).dx; |
| |
| for (int 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 = BuildState( |
| cocoonService: DevelopmentCocoonService(DateTime.utc(2020)), |
| authService: MockGoogleSignInService(), |
| ); |
| 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 LatticeScrollView lattice = find.byType(LatticeScrollView).evaluate().first.widget as LatticeScrollView; |
| |
| expect(lattice.cells.length, rows); |
| for (final List<LatticeCell> 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); |
| |
| // QualifiedTask (column) filters |
| await testGrid(tester, TaskGridFilter()..taskFilter = RegExp('Linux_android 2'), 27, 12); |
| |
| // CommitStatus (row) filters |
| await testGrid(tester, TaskGridFilter()..authorFilter = RegExp('bob'), 8, 101); |
| await testGrid(tester, TaskGridFilter()..messageFilter = RegExp('developer'), 18, 101); |
| await testGrid(tester, TaskGridFilter()..hashFilter = RegExp('2d22b5e85f986f3fa2cf1bfaf085905c2182c270'), 4, 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 List<CommitStatus> statusesWithSkips = <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..name = '1' |
| ..builderName = '1' |
| ..status = TaskBox.statusSucceeded, |
| ], |
| ), |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..name = '2' |
| ..builderName = '2' |
| ..status = TaskBox.statusSucceeded, |
| ], |
| ), |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..name = '3' |
| ..builderName = '3' |
| ..status = TaskBox.statusSucceeded, |
| ], |
| ), |
| ]; |
| |
| 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 List<CommitStatus> statuses = <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = StageName.cocoon |
| ..name = '1' |
| ..builderName = '1' |
| ..status = TaskBox.statusSucceeded, |
| ], |
| ), |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = StageName.luci |
| ..name = '1' |
| ..builderName = '1' |
| ..status = TaskBox.statusSucceeded, |
| ], |
| ), |
| ]; |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Material( |
| child: TaskGrid( |
| buildState: FakeBuildState(), |
| commitStatuses: statuses, |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.byType(LatticeScrollView), findsOneWidget); |
| final LatticeScrollView 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 creates a task icon row and they line up', (WidgetTester tester) async { |
| final List<CommitStatus> commitStatuses = <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..name = 'Task Name' |
| ..builderName = 'Task Name' |
| ..stageName = 'Stage Nome 1' |
| ..status = TaskBox.statusSucceeded, |
| Task() |
| ..name = 'Task Name' |
| ..builderName = 'Task Name' |
| ..stageName = 'Stage Nome 2' |
| ..status = TaskBox.statusFailed, |
| ], |
| ), |
| ]; |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Material( |
| child: TaskGrid( |
| buildState: FakeBuildState(), |
| commitStatuses: commitStatuses, |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.byType(TaskIcon), findsNWidgets(2)); |
| expect(tester.getTopLeft(find.byType(TaskIcon).at(0)).dy, tester.getTopLeft(find.byType(TaskIcon).at(1)).dy); |
| }); |
| |
| testWidgets('TaskGrid honors moreStatusesExist', (WidgetTester tester) async { |
| await precacheTaskIcons(tester); |
| final List<CommitStatus> commitStatuses = <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..name = 'Task Name' |
| ..name = 'Task Name' |
| ..stageName = 'Stage Nome' |
| ..status = TaskBox.statusSucceeded, |
| ], |
| ), |
| ]; |
| |
| 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: MockGoogleSignInService(), |
| cocoonService: MockCocoonService(), |
| ), |
| commitStatuses: <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Cast') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..status = 'Succeeded' |
| ..attempts = 2, |
| ], |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(find.byIcon(Icons.priority_high), findsOneWidget); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Material( |
| child: TaskGrid( |
| buildState: FakeBuildState( |
| authService: MockGoogleSignInService(), |
| cocoonService: MockCocoonService(), |
| ), |
| commitStatuses: <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Cast') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..status = '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: MockGoogleSignInService(), |
| cocoonService: MockCocoonService(), |
| ), |
| commitStatuses: <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Cast') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..status = 'Succeeded' |
| ..attempts = 1 |
| ..isTestFlaky = true, |
| ], |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(find.byIcon(Icons.priority_high), findsOneWidget); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Material( |
| child: TaskGrid( |
| buildState: FakeBuildState( |
| authService: MockGoogleSignInService(), |
| cocoonService: MockCocoonService(), |
| ), |
| commitStatuses: <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Cast') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..status = 'Succeeded' |
| ..attempts = 1 |
| ..isTestFlaky = false, |
| ], |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| expect(find.byIcon(Icons.priority_high), findsNothing); |
| }); |
| |
| testWidgets('TaskGrid shows icon for isTestFlaky tasks with multiple attempts', (WidgetTester tester) async { |
| final Task taskA3 = Task() |
| ..stageName = 'A' |
| ..builderName = '1' |
| ..name = 'A' |
| ..status = TaskBox.statusSucceeded |
| ..attempts = 3 |
| ..isTestFlaky = true; |
| |
| final Task taskB1 = Task() |
| ..stageName = 'B' |
| ..builderName = '2' |
| ..name = 'B' |
| ..status = TaskBox.statusSucceeded |
| ..attempts = 1 |
| ..isTestFlaky = false; |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Material( |
| child: TaskGrid( |
| buildState: FakeBuildState( |
| authService: MockGoogleSignInService(), |
| cocoonService: MockCocoonService(), |
| ), |
| commitStatuses: <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Cast') |
| ..tasks.addAll( |
| <Task>[ |
| taskA3, |
| ], |
| ), |
| CommitStatus() |
| ..commit = (Commit()..author = 'Cast') |
| ..tasks.addAll( |
| <Task>[taskB1], |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| 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 latticeScrollView = tester.firstWidget(find.byType(LatticeScrollView)); |
| final List<List<LatticeCell>> cells = latticeScrollView.cells; |
| final List<LatticeCell> myCells = cells.first; |
| expect(myCells.length, 3); |
| myCells.removeAt(0); // the first element is the github author box. |
| expect(myCells[0].taskName, 'A'); |
| expect(myCells[1].taskName, 'B'); |
| }); |
| |
| testWidgets('TaskGrid can handle all the various different statuses', (WidgetTester tester) async { |
| await precacheTaskIcons(tester); |
| final List<CommitStatus> statuses = <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..name = '1' |
| ..builderName = '1' |
| ..status = TaskBox.statusFailed, |
| Task() |
| ..stageName = 'A' |
| ..name = '2' |
| ..builderName = '2' |
| ..status = TaskBox.statusNew, |
| Task() |
| ..stageName = 'A' |
| ..name = '3' |
| ..builderName = '3' |
| ..status = TaskBox.statusSkipped, |
| Task() |
| ..stageName = 'A' |
| ..name = '4' |
| ..builderName = '4' |
| ..status = TaskBox.statusSucceeded, |
| Task() |
| ..stageName = 'A' |
| ..name = '5' |
| ..builderName = '5' |
| ..status = TaskBox.statusInProgress, |
| Task()..status = 'Invalid value', |
| ], |
| ), |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..name = '1' |
| ..builderName = '1' |
| ..attempts = 2 |
| ..status = TaskBox.statusFailed, |
| Task() |
| ..stageName = 'A' |
| ..name = '2' |
| ..builderName = '2' |
| ..attempts = 2 |
| ..status = TaskBox.statusNew, |
| Task() |
| ..stageName = 'A' |
| ..name = '3' |
| ..builderName = '3' |
| ..attempts = 2 |
| ..status = TaskBox.statusSkipped, |
| Task() |
| ..stageName = 'A' |
| ..name = '4' |
| ..builderName = '4' |
| ..attempts = 2 |
| ..status = TaskBox.statusSucceeded, |
| Task() |
| ..stageName = 'A' |
| ..name = '5' |
| ..builderName = '5' |
| ..attempts = 2 |
| ..status = TaskBox.statusInProgress, |
| Task()..status = 'Invalid value', |
| ], |
| ), |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..name = '1' |
| ..builderName = '1' |
| ..isFlaky = true |
| ..status = TaskBox.statusFailed, |
| Task() |
| ..stageName = 'A' |
| ..name = '2' |
| ..builderName = '2' |
| ..isFlaky = true |
| ..status = TaskBox.statusNew, |
| Task() |
| ..stageName = 'A' |
| ..name = '3' |
| ..builderName = '3' |
| ..isFlaky = true |
| ..status = TaskBox.statusSkipped, |
| Task() |
| ..stageName = 'A' |
| ..name = '4' |
| ..builderName = '4' |
| ..isFlaky = true |
| ..status = TaskBox.statusSucceeded, |
| Task() |
| ..stageName = 'A' |
| ..name = '5' |
| ..builderName = '5' |
| ..isFlaky = true |
| ..status = TaskBox.statusInProgress, |
| Task()..status = 'Invalid value', |
| ], |
| ), |
| CommitStatus() |
| ..commit = (Commit()..author = 'Author') |
| ..tasks.addAll( |
| <Task>[ |
| Task() |
| ..stageName = 'A' |
| ..name = '1' |
| ..builderName = '1' |
| ..attempts = 2 |
| ..isFlaky = true |
| ..status = TaskBox.statusFailed, |
| Task() |
| ..stageName = 'A' |
| ..name = '2' |
| ..builderName = '2' |
| ..attempts = 2 |
| ..isFlaky = true |
| ..status = TaskBox.statusNew, |
| Task() |
| ..stageName = 'A' |
| ..name = '3' |
| ..builderName = '3' |
| ..attempts = 2 |
| ..isFlaky = true |
| ..status = TaskBox.statusSkipped, |
| Task() |
| ..stageName = 'A' |
| ..name = '4' |
| ..builderName = '4' |
| ..attempts = 2 |
| ..isFlaky = true |
| ..status = TaskBox.statusSucceeded, |
| Task() |
| ..stageName = 'A' |
| ..name = '5' |
| ..builderName = '5' |
| ..attempts = 2 |
| ..isFlaky = true |
| ..status = TaskBox.statusInProgress, |
| Task()..status = 'Invalid value', |
| ], |
| ), |
| ]; |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(useMaterial3: false), |
| home: Material( |
| child: TaskGrid( |
| buildState: FakeBuildState(), |
| commitStatuses: statuses, |
| ), |
| ), |
| ), |
| ); |
| |
| await expectGoldenMatches(find.byType(TaskGrid), 'task_grid_test.differentTypes.png'); |
| }); |
| |
| // Table Driven Approach to ensure every message does show the corresponding color |
| TaskBox.statusColor.forEach((String message, Color color) { |
| testWidgets('Is the color $color when given the message $message', (WidgetTester tester) async { |
| await expectTaskBoxColorWithMessage(tester, message, color); |
| }); |
| }); |
| } |
| |
| Future<void> 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: cellPixelSize, |
| width: cellPixelSize, |
| child: RepaintBoundary( |
| child: TaskGrid( |
| buildState: FakeBuildState( |
| authService: MockGoogleSignInService(), |
| cocoonService: MockCocoonService(), |
| ), |
| commitStatuses: <CommitStatus>[ |
| CommitStatus() |
| ..commit = (Commit()..author = 'Mathilda') |
| ..tasks.addAll( |
| <Task>[Task()..status = message], |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| final RenderRepaintBoundary? renderObject = |
| tester.renderObject(find.byType(TaskGrid)).parent as RenderRepaintBoundary?; |
| final ByteData? pixels = await tester.runAsync<ByteData?>(() async { |
| return (await renderObject!.toImage()).toByteData(); |
| }); |
| expect(pixels!.lengthInBytes, (cellPixelArea * 4).round()); |
| const double padding = 4.0; |
| final int rgba = pixels.getUint32((((cellPixelSize * (cellSize + padding)) + cellSize + padding).ceil()) * 4); |
| expect((rgba >> 8) | (rgba << 24) & 0xFFFFFFFF, expectedColor.value); |
| } |