Files

127 lines
4.0 KiB
Dart

import 'dart:convert';
import 'package:ac_web/Api/Api.dart';
import 'package:ac_web/Layouts/DesktopView.dart';
import 'package:ac_web/Layouts/MainApp.dart';
import 'package:ac_web/Layouts/MobileView.dart';
import 'package:ac_web/Pages/AgentAvailabilities.dart';
import 'package:ac_web/Widgets/LatestReportEntry.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
/// The pages talk to `AcApi.instance`, so every test runs against a mocked
/// backend instead of a real one.
void configureMockedApi() {
final client = MockClient((request) async {
final String path = request.url.path;
if (path.endsWith('agents')) {
return http.Response(
jsonEncode([
{
'Name': 'Ada',
'Type': 'CODER',
'LastSeen': '2026-08-28T12:00:00Z',
'Status': 'WORKING',
'Tasks': <dynamic>[],
},
]),
200,
headers: {'content-type': 'application/json'},
);
}
if (path.endsWith('reports')) {
return http.Response(
jsonEncode([
{
'Id': 'report-1',
'Title': 'Nightly log',
'Author': 'Ada',
'Timestamp': '2026-08-27T10:00:00Z',
'Type': 'LOG',
'Text': 'All good.',
},
]),
200,
headers: {'content-type': 'application/json'},
);
}
return http.Response('Not found', 404);
});
AcApi.configure(const ApiConfig(host: 'api.local', port: 5000), httpClient: client);
}
/// Lets the futures of the mocked api complete. `pumpAndSettle` cannot be used
/// here because the loading state shows an endlessly spinning indicator.
Future<void> settleApiCalls(WidgetTester tester) async {
await tester.pump();
await tester.pump();
}
/// Resizes the test window and restores it again after the test.
void setWindowSize(WidgetTester tester, Size size) {
tester.view.physicalSize = size;
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.reset);
}
void main() {
setUp(configureMockedApi);
tearDown(AcApi.reset);
testWidgets('narrow windows show the mobile layout with its navigation bar', (tester) async {
setWindowSize(tester, const Size(400, 800));
await tester.pumpWidget(const MainApp());
expect(find.byType(MobileView), findsOneWidget);
expect(find.byType(DesktopView), findsNothing);
expect(find.byType(NavigationBar), findsOneWidget);
// The latest reports page is the one the mobile view starts on.
expect(find.byType(CircularProgressIndicator), findsOneWidget);
await settleApiCalls(tester);
expect(find.byType(LatestReportEntry), findsOneWidget);
expect(find.text('Nightly log'), findsOneWidget);
});
testWidgets('wide windows show all three columns next to each other', (tester) async {
setWindowSize(tester, const Size(DesktopView.minimumWidth + 200, 900));
await tester.pumpWidget(const MainApp());
await settleApiCalls(tester);
expect(find.byType(DesktopView), findsOneWidget);
expect(find.byType(MobileView), findsNothing);
// Reports on the left, the latest reports in the center, agents on the right.
expect(find.text('Code Review'), findsOneWidget);
expect(find.byType(LatestReportEntry), findsOneWidget);
expect(find.byType(AgentAvailabilities), findsOneWidget);
expect(find.text('Ada'), findsOneWidget);
});
testWidgets('a failing api is reported instead of spinning forever', (tester) async {
AcApi.configure(
const ApiConfig(host: 'api.local', port: 5000),
httpClient: MockClient((request) async => throw http.ClientException('offline')),
);
setWindowSize(tester, const Size(400, 800));
await tester.pumpWidget(const MainApp());
await settleApiCalls(tester);
expect(find.byType(CircularProgressIndicator), findsNothing);
expect(find.textContaining('Could not load reports'), findsOneWidget);
});
}