Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions dashboard/lib/state/presubmit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,17 @@ class PresubmitState extends ChangeNotifier {
false;
}

void resume() {
if (!_active) return;
_startTimer();
_fetchRefreshUpdate();
}

void pause() {
refreshTimer?.cancel();
refreshTimer = null;
}

void _fetchRefreshUpdate() {
if (!_active) return;
fetchIfNeeded();
Expand Down
19 changes: 18 additions & 1 deletion dashboard/lib/views/presubmit_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,16 @@ final class PreSubmitView extends StatefulWidget {
State<PreSubmitView> createState() => _PreSubmitViewState();
}

class _PreSubmitViewState extends State<PreSubmitView> {
class _PreSubmitViewState extends State<PreSubmitView>
with WidgetsBindingObserver {
PresubmitState? _presubmitState;

@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
Expand All @@ -59,10 +66,20 @@ class _PreSubmitViewState extends State<PreSubmitView> {

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_presubmitState?.removeListener(_onStateChanged);
super.dispose();
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_presubmitState?.resume();
} else {
_presubmitState?.pause();
}
}

@override
void didUpdateWidget(PreSubmitView oldWidget) {
super.didUpdateWidget(oldWidget);
Expand Down
11 changes: 11 additions & 0 deletions dashboard/test/state/presubmit_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,17 @@ void main() {
expect(presubmitState.refreshTimer?.isActive, isFalse);
});

test('PresubmitState pause and resume timer management', () async {
presubmitState.addListener(() {}); // Trigger timer start
expect(presubmitState.refreshTimer, isNotNull);

presubmitState.pause();
expect(presubmitState.refreshTimer, isNull);

presubmitState.resume();
expect(presubmitState.refreshTimer, isNotNull);
});

test(
'PresubmitState refreshes on auth change when becoming authenticated',
() async {
Expand Down
146 changes: 79 additions & 67 deletions dashboard/test/views/presubmit_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -281,82 +281,94 @@ void main() {
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},
),
],
);
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);

when(
mockCocoonService.fetchPresubmitGuard(
repo: anyNamed('repo'),
sha: mockSha,
),
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));
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.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
when(
mockCocoonService.fetchPresubmitGuard(
repo: anyNamed('repo'),
sha: mockSha,
),
]),
);
).thenAnswer((_) async => const CocoonResponse.data(guardResponse));

await tester.runAsync(() async {
await tester.pumpWidget(
createPreSubmitView({'repo': 'flutter', 'pr': '123'}),
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
),
]),
);
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.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();

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;
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();
});
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);
});
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',
Expand Down
Loading