blob: e3b1e4bdc6597f371797da48eb1868b13833439b [file] [edit]
// 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:async';
import 'package:cocoon_common/guard_status.dart';
import 'package:cocoon_common/rpc_model.dart';
import 'package:cocoon_common/task_status.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_icons/flutter_app_icons_platform_interface.dart';
import 'package:flutter_dashboard/service/cocoon.dart';
import 'package:flutter_dashboard/state/build.dart';
import 'package:flutter_dashboard/state/presubmit.dart';
import 'package:flutter_dashboard/views/presubmit_view.dart';
import 'package:flutter_dashboard/widgets/sha_selector.dart';
import 'package:flutter_dashboard/widgets/state_provider.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import '../utils/fake_flutter_app_icons.dart';
import '../utils/mocks.dart';
void main() {
late MockCocoonService mockCocoonService;
late MockFirebaseAuthService mockAuthService;
late BuildState buildState;
late PresubmitState presubmitState;
setUp(() {
mockCocoonService = MockCocoonService();
mockAuthService = MockFirebaseAuthService();
FlutterAppIconsPlatform.instance = FakeFlutterAppIcons();
when(mockAuthService.user).thenReturn(null);
when(mockAuthService.isAuthenticated).thenReturn(false);
when(mockAuthService.idToken).thenAnswer((_) async => 'fakeToken');
when(
mockCocoonService.fetchFlutterBranches(),
).thenAnswer((_) async => const CocoonResponse.data([]));
when(
mockCocoonService.fetchRepos(),
).thenAnswer((_) async => const CocoonResponse.data([]));
when(
mockCocoonService.fetchCommitStatuses(
branch: anyNamed('branch'),
repo: anyNamed('repo'),
),
).thenAnswer((_) async => const CocoonResponse.data([]));
when(
mockCocoonService.fetchTreeBuildStatus(
branch: anyNamed('branch'),
repo: anyNamed('repo'),
),
).thenAnswer(
(_) async => CocoonResponse.data(
BuildStatusResponse(buildStatus: BuildStatus.success, failingTasks: []),
),
);
when(
mockCocoonService.fetchSuppressedTests(repo: anyNamed('repo')),
).thenAnswer((_) async => const CocoonResponse.data([]));
when(
mockCocoonService.fetchPresubmitGuardSummaries(
repo: anyNamed('repo'),
pr: anyNamed('pr'),
),
).thenAnswer(
(_) async => const CocoonResponse.data([
PresubmitGuardSummary(
headSha: 'decaf_3_real_sha',
creationTime: 123456789,
guardStatus: GuardStatus.succeeded,
),
PresubmitGuardSummary(
headSha: 'face5_2_mock_sha',
creationTime: 123456789,
guardStatus: GuardStatus.failed,
),
PresubmitGuardSummary(
headSha: 'cafe5_1_mock_sha',
creationTime: 123456789,
guardStatus: GuardStatus.inProgress,
),
]),
);
when(
mockCocoonService.fetchPresubmitGuard(
repo: anyNamed('repo'),
sha: anyNamed('sha'),
),
).thenAnswer(
(_) async => const CocoonResponse.error('Not found', statusCode: 404),
);
when(
mockCocoonService.rerunFailedJob(
idToken: anyNamed('idToken'),
repo: anyNamed('repo'),
pr: anyNamed('pr'),
jobName: anyNamed('jobName'),
),
).thenAnswer((_) async => const CocoonResponse<void>.data(null));
when(
mockCocoonService.rerunAllFailedJobs(
idToken: anyNamed('idToken'),
repo: anyNamed('repo'),
pr: anyNamed('pr'),
),
).thenAnswer((_) async => const CocoonResponse<void>.data(null));
when(
mockCocoonService.fetchPresubmitJobDetails(
checkRunId: anyNamed('checkRunId'),
jobName: anyNamed('jobName'),
),
).thenAnswer((_) async => const CocoonResponse.data([]));
buildState = BuildState(
cocoonService: mockCocoonService,
authService: mockAuthService,
);
presubmitState = PresubmitState(
cocoonService: mockCocoonService,
authService: mockAuthService,
);
});
Widget createPreSubmitView(Map<String, String> queryParameters) {
presubmitState.syncUpdate(
repo: queryParameters['repo'],
pr: queryParameters['pr'],
sha: queryParameters['sha'],
);
return Material(
child: StateProvider(
buildState: buildState,
presubmitState: presubmitState,
signInService: mockAuthService,
child: MaterialApp(
home: PreSubmitView(
queryParameters: queryParameters,
syncNavigation: false,
),
),
),
);
}
testWidgets(
'PreSubmitView displays correct title and status with repo and sha',
(WidgetTester tester) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const guardResponse = PresubmitGuardResponse(
prNum: 123,
author: 'dash',
guardStatus: GuardStatus.succeeded,
checkRunId: 456,
stages: [],
);
when(
mockCocoonService.fetchPresubmitGuard(repo: 'flutter', sha: 'abc'),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'sha': 'abc'}),
);
for (var i = 0; i < 50; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('by dash').evaluate().isNotEmpty) break;
}
});
await tester.pumpAndSettle();
expect(find.textContaining('PR #123 by dash (abc)'), findsOneWidget);
expect(find.textContaining('Succeeded'), findsOneWidget);
},
);
testWidgets('PreSubmitView displays mocked data and switches tabs', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const mockSha = 'decaf_3_real_sha';
const guardResponse = PresubmitGuardResponse(
prNum: 123,
author: 'dash',
guardStatus: GuardStatus.failed,
checkRunId: 456,
stages: [
PresubmitGuardStage(
name: 'Engine',
createdAt: 0,
builds: {'Mac mac_host_engine 1': TaskStatus.failed},
),
],
);
when(
mockCocoonService.fetchPresubmitGuard(
repo: anyNamed('repo'),
sha: mockSha,
),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
when(
mockCocoonService.fetchPresubmitJobDetails(
checkRunId: anyNamed('checkRunId'),
jobName: argThat(contains('mac_host_engine'), named: 'jobName'),
),
).thenAnswer(
(_) async => CocoonResponse.data([
PresubmitJobResponse(
attemptNumber: 1,
jobName: 'Mac mac_host_engine 1',
creationTime: 0,
status: TaskStatus.succeeded,
summary: 'All tests passed (452/452)',
),
PresubmitJobResponse(
attemptNumber: 2,
jobName: 'Mac mac_host_engine 1',
creationTime: 0,
status: TaskStatus.failed,
summary: 'Test failed: Unit Tests',
),
]),
);
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'pr': '123'}),
);
for (var i = 0; i < 50; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('by dash').evaluate().isNotEmpty) break;
}
});
await tester.pumpAndSettle();
expect(find.textContaining('PR #123'), findsOneWidget);
await tester.tap(find.textContaining('mac_host_engine').first);
await tester.runAsync(() async {
for (var i = 0; i < 50; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('All tests passed').evaluate().isNotEmpty) {
break;
}
}
});
await tester.pumpAndSettle();
expect(find.textContaining('All tests passed'), findsOneWidget);
expect(find.textContaining('Status: Succeeded'), findsOneWidget);
await tester.tap(find.text('#2'));
await tester.pumpAndSettle();
expect(find.textContaining('Test failed: Unit Tests'), findsOneWidget);
expect(find.textContaining('Status: Failed'), findsOneWidget);
});
testWidgets(
'PreSubmitView displays default job details when summary is empty',
(WidgetTester tester) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const mockSha = 'decaf_3_real_sha';
const guardResponse = PresubmitGuardResponse(
prNum: 123,
author: 'dash',
guardStatus: GuardStatus.failed,
checkRunId: 456,
stages: [
PresubmitGuardStage(
name: 'Engine',
createdAt: 0,
builds: {'Mac mac_host_engine 1': TaskStatus.failed},
),
],
);
when(
mockCocoonService.fetchPresubmitGuard(
repo: anyNamed('repo'),
sha: mockSha,
),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
when(
mockCocoonService.fetchPresubmitJobDetails(
checkRunId: anyNamed('checkRunId'),
jobName: argThat(contains('mac_host_engine'), named: 'jobName'),
),
).thenAnswer(
(_) async => CocoonResponse.data([
PresubmitJobResponse(
attemptNumber: 1,
jobName: 'Mac mac_host_engine 1',
creationTime: 0,
status: TaskStatus.failed,
summary: '', // Empty summary
),
]),
);
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'pr': '123'}),
);
for (var i = 0; i < 50; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('by dash').evaluate().isNotEmpty) break;
}
});
await tester.pumpAndSettle();
expect(find.textContaining('PR #123'), findsOneWidget);
await tester.tap(find.textContaining('mac_host_engine').first);
await tester.runAsync(() async {
for (var i = 0; i < 50; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find
.textContaining('Mac mac_host_engine 1 failed.')
.evaluate()
.isNotEmpty) {
break;
}
}
});
await tester.pumpAndSettle();
expect(
find.textContaining('Mac mac_host_engine 1 failed.'),
findsOneWidget,
);
expect(
find.textContaining(
'Click "View more details on LUCI UI" button below for more details.',
),
findsOneWidget,
);
},
);
testWidgets(
'PreSubmitView automatically selects latest SHA and updates sidebar when opened with PR only',
(WidgetTester tester) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const latestSha = 'decaf_3_real_sha';
const guardResponse = PresubmitGuardResponse(
prNum: 123,
author: 'dash',
guardStatus: GuardStatus.failed,
checkRunId: 456,
stages: [
PresubmitGuardStage(
name: 'Engine',
createdAt: 0,
builds: {'Mac mac_host_engine 1': TaskStatus.failed},
),
],
);
when(
mockCocoonService.fetchPresubmitGuard(
repo: anyNamed('repo'),
sha: latestSha,
),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'pr': '123'}),
);
// Wait for summaries, then latest SHA selection, then guard status fetch
for (var i = 0; i < 50; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
final state = Provider.of<PresubmitState>(
tester.element(find.byType(PreSubmitView)),
listen: false,
);
if (state.guardResponse != null) break;
}
});
await tester.pumpAndSettle();
final state = Provider.of<PresubmitState>(
tester.element(find.byType(PreSubmitView)),
listen: false,
);
expect(state.sha, latestSha);
expect(find.textContaining('by dash'), findsOneWidget);
expect(find.textContaining('mac_host_engine'), findsOneWidget);
},
);
testWidgets('PreSubmitView SHA dropdown switches mock SHAs', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'pr': '123'}),
);
for (var i = 0; i < 20; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.byType(ShaSelector).evaluate().isNotEmpty) break;
}
});
await tester.pumpAndSettle();
expect(find.byType(ShaSelector), findsOneWidget);
await tester.tap(find.byType(ShaSelector));
await tester.pumpAndSettle();
await tester.tap(
find.byWidgetPredicate(
(widget) =>
widget is DropdownMenuItem<String> &&
widget.value == 'face5_2_mock_sha',
),
);
await tester.pumpAndSettle();
expect(find.byType(ShaSelector), findsOneWidget);
expect(find.textContaining('face5_2'), findsOneWidget);
expect(find.text('Re-run failed'), findsNothing);
expect(find.text('Re-run'), findsNothing);
});
testWidgets('PreSubmitView functional sha route fetches check details', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const guardResponse = PresubmitGuardResponse(
prNum: 123,
checkRunId: 456,
author: 'dash',
stages: [
PresubmitGuardStage(
name: 'Engine',
createdAt: 0,
builds: {'Mac mac_host_engine': TaskStatus.succeeded},
),
],
guardStatus: GuardStatus.succeeded,
);
when(
mockCocoonService.fetchPresubmitGuard(repo: 'flutter', sha: 'abc'),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
when(
mockCocoonService.fetchPresubmitJobDetails(
checkRunId: 456,
jobName: 'Mac mac_host_engine',
),
).thenAnswer(
(_) async => CocoonResponse.data([
PresubmitJobResponse(
attemptNumber: 1,
jobName: 'Mac mac_host_engine',
creationTime: 0,
status: TaskStatus.succeeded,
summary: 'Live log content',
),
]),
);
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'sha': 'abc'}),
);
for (var i = 0; i < 20; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('by dash').evaluate().isNotEmpty) break;
}
});
await tester.pumpAndSettle();
await tester.tap(find.textContaining('mac_host_engine').first);
await tester.runAsync(() async {
for (var i = 0; i < 20; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('Live log content').evaluate().isNotEmpty) {
break;
}
}
});
await tester.pumpAndSettle();
expect(find.textContaining('Live log content'), findsOneWidget);
});
testWidgets('PreSubmitView meets accessibility guidelines', (
WidgetTester tester,
) async {
final handle = tester.ensureSemantics();
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(createPreSubmitView({'repo': 'flutter'}));
await tester.pumpAndSettle();
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
handle.dispose();
});
group('PreSubmitView Header Text', () {
testWidgets('displays loading text when navigated via PR', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'pr': '123'}),
);
expect(find.textContaining('PR #123'), findsOneWidget);
});
testWidgets(
'displays empty header text when neither PR nor SHA is provided',
(WidgetTester tester) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(createPreSubmitView({'repo': 'flutter'}));
expect(find.text(''), findsOneWidget);
},
);
testWidgets('displays loading text when navigated via SHA', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'sha': 'abcdef123456'}),
);
expect(find.text('(abcdef1)'), findsOneWidget);
});
testWidgets('displays full header text when loaded', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const guardResponse = PresubmitGuardResponse(
prNum: 123,
author: 'dash',
guardStatus: GuardStatus.succeeded,
checkRunId: 456,
stages: [],
);
when(
mockCocoonService.fetchPresubmitGuard(
repo: 'flutter',
sha: 'abcdef123456',
),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'sha': 'abcdef123456'}),
);
for (var i = 0; i < 20; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('by dash').evaluate().isNotEmpty) break;
}
});
await tester.pumpAndSettle();
expect(find.textContaining('PR #123 by dash (abcdef1)'), findsOneWidget);
});
});
group('Re-run functionality', () {
testWidgets('Re-run buttons are disabled when unauthenticated', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const guardResponse = PresubmitGuardResponse(
prNum: 123,
author: 'dash',
guardStatus: GuardStatus.failed,
checkRunId: 456,
stages: [
PresubmitGuardStage(
name: 'Engine',
createdAt: 0,
builds: {'linux_bot': TaskStatus.failed},
),
],
);
when(
mockCocoonService.fetchPresubmitGuard(
repo: 'flutter',
sha: 'decaf_3_real_sha',
),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({
'repo': 'flutter',
'pr': '123',
'sha': 'decaf_3_real_sha',
}),
);
for (var i = 0; i < 20; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('linux_bot').evaluate().isNotEmpty) break;
}
});
await tester.pumpAndSettle();
final rerunAllButton = find.widgetWithText(TextButton, 'Re-run failed');
final rerunButton = find.widgetWithText(TextButton, 'Re-run');
expect(rerunAllButton, findsOneWidget);
expect(rerunButton, findsOneWidget);
// Verify buttons are disabled (onPressed is null)
expect(tester.widget<TextButton>(rerunAllButton).onPressed, isNull);
expect(tester.widget<TextButton>(rerunButton).onPressed, isNull);
});
testWidgets('Re-run buttons are enabled when authenticated', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
when(mockAuthService.isAuthenticated).thenReturn(true);
const guardResponse = PresubmitGuardResponse(
prNum: 123,
author: 'dash',
guardStatus: GuardStatus.failed,
checkRunId: 456,
stages: [
PresubmitGuardStage(
name: 'Engine',
createdAt: 0,
builds: {'linux_bot': TaskStatus.failed},
),
],
);
when(
mockCocoonService.fetchPresubmitGuard(
repo: 'flutter',
sha: 'decaf_3_real_sha',
),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({
'repo': 'flutter',
'pr': '123',
'sha': 'decaf_3_real_sha',
}),
);
for (var i = 0; i < 20; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('linux_bot').evaluate().isNotEmpty) break;
}
});
await tester.pumpAndSettle();
final rerunAllButton = find.widgetWithText(TextButton, 'Re-run failed');
final rerunButton = find.widgetWithText(TextButton, 'Re-run');
expect(tester.widget<TextButton>(rerunAllButton).onPressed, isNotNull);
expect(tester.widget<TextButton>(rerunButton).onPressed, isNotNull);
});
testWidgets('Re-run buttons are disabled while re-running', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
when(mockAuthService.isAuthenticated).thenReturn(true);
const guardResponse = PresubmitGuardResponse(
prNum: 123,
author: 'dash',
guardStatus: GuardStatus.failed,
checkRunId: 456,
stages: [
PresubmitGuardStage(
name: 'Engine',
createdAt: 0,
builds: {'linux_bot': TaskStatus.failed},
),
],
);
final rerunCompleter = Completer<CocoonResponse<void>>();
when(
mockCocoonService.rerunAllFailedJobs(
idToken: anyNamed('idToken'),
repo: anyNamed('repo'),
pr: anyNamed('pr'),
),
).thenAnswer((_) => rerunCompleter.future);
when(
mockCocoonService.fetchPresubmitGuard(
repo: 'flutter',
sha: 'decaf_3_real_sha',
),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({
'repo': 'flutter',
'pr': '123',
'sha': 'decaf_3_real_sha',
}),
);
for (var i = 0; i < 20; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('linux_bot').evaluate().isNotEmpty) break;
}
});
await tester.pumpAndSettle();
final rerunAllButton = find.widgetWithText(TextButton, 'Re-run failed');
final rerunButton = find.widgetWithText(TextButton, 'Re-run');
// Start re-running
final rerunFuture = presubmitState.rerunAllFailedJobs();
await tester.pump();
expect(tester.widget<TextButton>(rerunAllButton).onPressed, isNull);
expect(tester.widget<TextButton>(rerunButton).onPressed, isNull);
// Finish re-running
rerunCompleter.complete(const CocoonResponse<void>.data(null));
await rerunFuture;
await tester.pump(
const Duration(seconds: 2),
); // Pump time for the refresh timer
expect(tester.widget<TextButton>(rerunAllButton).onPressed, isNotNull);
expect(tester.widget<TextButton>(rerunButton).onPressed, isNotNull);
});
});
group('PreSubmitView Sorting', () {
testWidgets('checks are sorted by status priority and then name', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(2000, 1080);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const guardResponse = PresubmitGuardResponse(
prNum: 123,
author: 'dash',
guardStatus: GuardStatus.failed,
checkRunId: 456,
stages: [
PresubmitGuardStage(
name: 'Engine',
createdAt: 0,
builds: {
'succeeded_z': TaskStatus.succeeded,
'failed_b': TaskStatus.failed,
'infra_c': TaskStatus.infraFailure,
'in_progress_d': TaskStatus.inProgress,
'new_e': TaskStatus.waitingForBackfill,
'cancelled_f': TaskStatus.cancelled,
'skipped_g': TaskStatus.skipped,
'failed_a': TaskStatus.failed,
'succeeded_u': TaskStatus.succeeded,
},
),
],
);
when(
mockCocoonService.fetchPresubmitGuard(
repo: 'flutter',
sha: 'decaf_3_real_sha',
),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({
'repo': 'flutter',
'pr': '123',
'sha': 'decaf_3_real_sha',
}),
);
for (var i = 0; i < 20; i++) {
await tester.pump();
await Future<void>.delayed(const Duration(milliseconds: 50));
if (find.textContaining('succeeded_z').evaluate().isNotEmpty) break;
}
});
await tester.pump(); // Render the results
// The expected order should be:
// 1. failed_a (Failed)
// 2. failed_b (Failed)
// 3. infra_c (Infra Failure)
// 4. in_progress_d (In Progress)
// 5. new_e (New)
// 6. cancelled_f (Cancelled)
// 7. skipped_g (Skipped)
// 8. succeeded_u (Succeeded)
// 9. succeeded_z (Succeeded)
final names = tester
.widgetList<Text>(
find.descendant(
of: find.byType(PreSubmitView),
matching: find.byWidgetPredicate(
(widget) =>
widget is Text &&
(widget.style?.fontSize == 14 ||
widget.style?.fontSize == 11),
),
),
)
.map((t) => t.data!)
.where((name) => name != 'ENGINE')
.take(9)
.toList();
expect(names, [
'failed_a',
'failed_b',
'infra_c',
'in_progress_d',
'new_e',
'cancelled_f',
'skipped_g',
'succeeded_u',
'succeeded_z',
]);
});
});
}