focus-flow/apps/focus_flow_flutter/test/widget_test.dart

172 lines
5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:scheduler_core/scheduler_core.dart';
import 'package:focus_flow_flutter/controllers/scheduler_read_controller.dart';
import 'package:focus_flow_flutter/main.dart';
import 'package:focus_flow_flutter/widgets/backlog_pane.dart';
void main() {
testWidgets('renders Today and Backlog read models', (tester) async {
await tester.pumpWidget(
FocusFlowApp(composition: DemoSchedulerComposition.seeded()),
);
await _pumpAndSettle(tester);
expect(find.text('FocusFlow'), findsOneWidget);
expect(find.text('Today'), findsWidgets);
expect(find.text('Plan the day'), findsWidgets);
expect(find.text('Appointment'), findsWidgets);
await tester.tap(find.byIcon(Icons.inbox_outlined));
await _pumpAndSettle(tester);
expect(find.text('Backlog'), findsWidgets);
expect(find.text('Send project update'), findsOneWidget);
expect(find.text('Sort receipts'), findsOneWidget);
});
testWidgets('renders compact panel from owner settings', (tester) async {
await tester.pumpWidget(
FocusFlowApp(composition: DemoSchedulerComposition.seeded()),
);
await _pumpAndSettle(tester);
expect(find.text('Compact'), findsOneWidget);
expect(find.text('Plan the day'), findsNWidgets(2));
});
testWidgets('renders Backlog empty state', (tester) async {
await tester.pumpWidget(
_testShell(
BacklogPane(
controller: _FakeReadController<BacklogQueryResult>(
SchedulerReadEmpty<BacklogQueryResult>(_emptyBacklog()),
),
),
),
);
expect(find.text('Backlog is clear'), findsOneWidget);
expect(
find.text('Captured tasks will appear here when they need planning.'),
findsOneWidget,
);
});
testWidgets('typed failure state can retry', (tester) async {
final controller = _FakeReadController<BacklogQueryResult>(
SchedulerReadFailure<BacklogQueryResult>(
failure: const ApplicationFailure(
code: ApplicationFailureCode.conflict,
),
),
retryState: SchedulerReadEmpty<BacklogQueryResult>(_emptyBacklog()),
);
await tester.pumpWidget(_testShell(BacklogPane(controller: controller)));
expect(find.text('Could not load Backlog'), findsOneWidget);
expect(find.text('Status: conflict'), findsOneWidget);
await tester.tap(find.text('Retry'));
await _pumpAndSettle(tester);
expect(controller.retryCount, 1);
expect(find.text('Backlog is clear'), findsOneWidget);
});
testWidgets('quick capture schedules into Today and completes', (
tester,
) async {
final composition = DemoSchedulerComposition.seeded(
includeTodayItems: false,
includeBacklogItems: false,
);
await tester.pumpWidget(FocusFlowApp(composition: composition));
await _pumpAndSettle(tester);
await tester.tap(find.byIcon(Icons.inbox_outlined));
await _pumpAndSettle(tester);
await tester.enterText(
find.byKey(const ValueKey('quick-capture-title')),
'Water plants',
);
await tester.tap(find.text('Capture'));
await _pumpAndSettle(tester);
expect(find.text('Water plants'), findsOneWidget);
expect(composition.store.currentTasks.single.status, TaskStatus.backlog);
await tester.enterText(find.byType(TextField).last, '20');
await tester.tap(find.byTooltip('Schedule'));
await _pumpAndSettle(tester);
await tester.tap(find.byIcon(Icons.today_outlined));
await _pumpAndSettle(tester);
expect(find.text('Water plants'), findsWidgets);
expect(composition.store.currentTasks.single.status, TaskStatus.planned);
await tester.tap(find.byTooltip('Done').first);
await _pumpAndSettle(tester);
expect(composition.store.currentTasks.single.status, TaskStatus.completed);
});
}
Future<int> _pumpAndSettle(WidgetTester tester) {
return tester.pumpAndSettle(
const Duration(milliseconds: 50),
EnginePhase.sendSemanticsUpdate,
const Duration(seconds: 2),
);
}
Widget _testShell(Widget child) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF2F6F73),
brightness: Brightness.dark,
),
useMaterial3: true,
),
home: Scaffold(body: child),
);
}
BacklogQueryResult _emptyBacklog() {
return BacklogQueryResult(
ownerId: 'owner-1',
settings: OwnerSettings(ownerId: 'owner-1', timeZoneId: 'UTC'),
items: const [],
);
}
class _FakeReadController<T> extends UiReadController<T> {
_FakeReadController(SchedulerReadState<T> state, {this.retryState})
: _state = state;
SchedulerReadState<T> _state;
final SchedulerReadState<T>? retryState;
int retryCount = 0;
@override
SchedulerReadState<T> get state => _state;
@override
Future<void> load() async {}
@override
Future<void> retry() async {
retryCount += 1;
final next = retryState;
if (next != null) {
_state = next;
notifyListeners();
}
}
}