forked from eva/focus-flow
172 lines
5 KiB
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();
|
|
}
|
|
}
|
|
}
|