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

111 lines
3.5 KiB
Dart

// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only
/// Tests persistent SQLite composition behavior in the Flutter app.
library;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:scheduler_core/scheduler_core.dart';
import 'package:focus_flow_flutter/app/focus_flow_app.dart';
import 'package:focus_flow_flutter/app/persistent_scheduler_composition.dart';
import 'package:focus_flow_flutter/controllers/scheduler_command_controller.dart';
/// Runs persistent composition smoke tests.
void main() {
testWidgets(
'persistent app starts without seeded demo tasks and reopens capture',
(tester) async {
final path = await _runAsync(tester, _tempDatabasePath);
final composition = await _runAsync(
tester,
() => PersistentSchedulerComposition.open(
sqlitePath: path,
selectedDate: _date,
clock: FixedClock(_instant(8)),
),
);
await tester.pumpWidget(FocusFlowApp(composition: composition));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
await tester.pump(const Duration(seconds: 1));
expect(find.text('January 2, 2026'), findsOneWidget);
expect(find.text('Clean coffee maker'), findsNothing);
expect(find.text('Water plants'), findsNothing);
final commandController = composition.createCommandController(
refreshReads: () async {},
);
addTearDown(commandController.dispose);
await tester.runAsync(
() => commandController.quickCaptureToBacklog(
'Captured from Flutter test',
),
);
expect(commandController.state, isA<SchedulerCommandSuccess>());
await tester.pumpWidget(const SizedBox.shrink());
await tester.pump();
await tester.runAsync(composition.dispose);
final reopened = await _runAsync(
tester,
() => PersistentSchedulerComposition.open(
sqlitePath: path,
selectedDate: _date,
clock: FixedClock(_instant(8, 10)),
),
);
addTearDown(() => tester.runAsync(reopened.dispose));
final backlog = await _runAsync(
tester,
() => reopened.managementUseCases.getBacklog(
GetBacklogRequest(
context: reopened.context('flutter-read-backlog'),
sortKey: BacklogSortKey.age,
),
),
);
expect(backlog.requireValue.items, hasLength(1));
expect(
backlog.requireValue.items.single.task.title,
'Captured from Flutter test',
);
},
);
}
/// Fixed date used by persistent Flutter tests.
final _date = CivilDate(2026, 1, 2);
/// Returns an instant on the fixed lifecycle date.
DateTime _instant(int hour, [int minute = 0]) {
return DateTime.utc(_date.year, _date.month, _date.day, hour, minute);
}
/// Returns a fresh temporary SQLite path.
Future<String> _tempDatabasePath() async {
final directory = await Directory.systemTemp.createTemp(
'focus-flow-flutter-persistence-',
);
addTearDown(() => directory.delete(recursive: true));
return '${directory.path}/scheduler.sqlite';
}
/// Runs real async work outside the widget-test fake async zone.
Future<T> _runAsync<T>(
WidgetTester tester,
Future<T> Function() callback,
) async {
final result = await tester.runAsync(callback);
if (result == null) {
throw StateError('runAsync returned null.');
}
return result;
}