Adds a getOwnerSettings read query to scheduler_core (previously only an internal helper existed) and an updateOwnerSettings command on the Flutter command controller, then builds a shared Settings dialog surfacing timezone, day window, compact mode, and backlog staleness thresholds. Both existing no-op entry points (sidebar nav item, top bar button) now open the same dialog and persist edits through the existing OwnerSettings/BacklogStalenessSettings backend. Per the design spec (circuitforge-plans/focus-flow/superpowers/specs/ 2026-07-07-settings-screen-design.md), the compact-mode toggle persists but is disclosed in-UI as not yet affecting Today rendering, since that wiring is separate scope (#11). Closes: #16
115 lines
3.5 KiB
Dart
115 lines
3.5 KiB
Dart
// SPDX-FileCopyrightText: 2026 FocusFlow contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
/// Tests app-level settings dialog behavior.
|
|
library;
|
|
|
|
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/demo_scheduler_composition.dart';
|
|
import 'package:focus_flow_flutter/app/focus_flow_app.dart';
|
|
|
|
/// Runs app-level settings dialog tests.
|
|
void main() {
|
|
testWidgets('sidebar Settings opens a dialog seeded with persisted values', (
|
|
tester,
|
|
) async {
|
|
await _pumpPlanApp(tester);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('nav-settings')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(AlertDialog), findsOneWidget);
|
|
expect(find.text('UTC'), findsOneWidget);
|
|
|
|
final compactSwitch = tester.widget<SwitchListTile>(
|
|
find.byKey(const ValueKey('settings-compact-mode')),
|
|
);
|
|
expect(compactSwitch.value, isTrue);
|
|
|
|
await tester.tap(find.text('Cancel'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(AlertDialog), findsNothing);
|
|
});
|
|
|
|
testWidgets('top bar Settings button opens the same dialog', (tester) async {
|
|
await _pumpPlanApp(tester);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('top-bar-settings-button')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const ValueKey('settings-timezone')), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Cancel'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('saving settings persists the change and reopens with it', (
|
|
tester,
|
|
) async {
|
|
await _pumpPlanApp(tester);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('nav-settings')));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const ValueKey('settings-timezone')));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('EST').last);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const ValueKey('settings-save')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(AlertDialog), findsNothing);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('nav-settings')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('EST'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Cancel'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('invalid staleness thresholds show a calm inline error', (
|
|
tester,
|
|
) async {
|
|
await _pumpPlanApp(tester);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('nav-settings')));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('settings-aging-days')),
|
|
'1',
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const ValueKey('settings-save')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(AlertDialog), findsOneWidget);
|
|
expect(
|
|
find.text('Aging must be a number of days at least as large as Fresh.'),
|
|
findsOneWidget,
|
|
);
|
|
|
|
await tester.tap(find.text('Cancel'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
}
|
|
|
|
/// Pumps the FocusFlow app at the desktop test size.
|
|
Future<void> _pumpPlanApp(WidgetTester tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(1586, 992));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(FocusFlowApp(composition: _composition()));
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
/// Builds the seeded demo composition used by settings dialog tests.
|
|
DemoSchedulerComposition _composition() {
|
|
return DemoSchedulerComposition.seeded(selectedDate: CivilDate(2025, 5, 20));
|
|
}
|