feat: wire date navigation controls
This commit is contained in:
parent
16c460db0a
commit
98fdd0792b
8 changed files with 272 additions and 15 deletions
|
|
@ -7,6 +7,7 @@ library;
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:scheduler_core/scheduler_core.dart';
|
||||||
|
|
||||||
import '../controllers/scheduler_command_controller.dart';
|
import '../controllers/scheduler_command_controller.dart';
|
||||||
import '../controllers/selected_date_controller.dart';
|
import '../controllers/selected_date_controller.dart';
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,31 @@ class _FocusFlowHomeState extends State<FocusFlowHome> {
|
||||||
await commandController.addGenericFlexibleTask();
|
await commandController.addGenericFlexibleTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Moves the visible planning date backward by one day.
|
||||||
|
void _selectPreviousDate() {
|
||||||
|
selectedDateController.selectPreviousDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the visible planning date forward by one day.
|
||||||
|
void _selectNextDate() {
|
||||||
|
selectedDateController.selectNextDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens the date picker and selects the chosen owner-local civil date.
|
||||||
|
Future<void> _chooseDate() async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _dateTimeForCivilDate(selectedDateController.date),
|
||||||
|
firstDate: DateTime(2000),
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
builder: _buildDatePickerTheme,
|
||||||
|
);
|
||||||
|
if (picked == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedDateController.selectDate(_civilDateForDateTime(picked));
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds the widget subtree for this component.
|
/// Builds the widget subtree for this component.
|
||||||
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
||||||
@override
|
@override
|
||||||
|
|
@ -91,6 +116,11 @@ class _FocusFlowHomeState extends State<FocusFlowHome> {
|
||||||
onCompleteCard: _toggleCardCompletion,
|
onCompleteCard: _toggleCardCompletion,
|
||||||
onAddTask: _addGenericFlexibleTask,
|
onAddTask: _addGenericFlexibleTask,
|
||||||
onClearSelection: controller.clearSelection,
|
onClearSelection: controller.clearSelection,
|
||||||
|
onPreviousDate: _selectPreviousDate,
|
||||||
|
onNextDate: _selectNextDate,
|
||||||
|
onChooseDate: () {
|
||||||
|
unawaited(_chooseDate());
|
||||||
|
},
|
||||||
),
|
),
|
||||||
TodayScreenReady(:final data) => _TodayScreenScaffold(
|
TodayScreenReady(:final data) => _TodayScreenScaffold(
|
||||||
data: data,
|
data: data,
|
||||||
|
|
@ -99,6 +129,11 @@ class _FocusFlowHomeState extends State<FocusFlowHome> {
|
||||||
onCompleteCard: _toggleCardCompletion,
|
onCompleteCard: _toggleCardCompletion,
|
||||||
onAddTask: _addGenericFlexibleTask,
|
onAddTask: _addGenericFlexibleTask,
|
||||||
onClearSelection: controller.clearSelection,
|
onClearSelection: controller.clearSelection,
|
||||||
|
onPreviousDate: _selectPreviousDate,
|
||||||
|
onNextDate: _selectNextDate,
|
||||||
|
onChooseDate: () {
|
||||||
|
unawaited(_chooseDate());
|
||||||
|
},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -106,4 +141,33 @@ class _FocusFlowHomeState extends State<FocusFlowHome> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds the dark theme wrapper used by the date picker dialog.
|
||||||
|
Widget _buildDatePickerTheme(BuildContext context, Widget? child) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Theme(
|
||||||
|
data: theme.copyWith(
|
||||||
|
colorScheme: theme.colorScheme.copyWith(
|
||||||
|
primary: FocusFlowTokens.accentMagenta,
|
||||||
|
onPrimary: FocusFlowTokens.textPrimary,
|
||||||
|
surface: FocusFlowTokens.elevatedPanel,
|
||||||
|
onSurface: FocusFlowTokens.textPrimary,
|
||||||
|
),
|
||||||
|
dialogTheme: theme.dialogTheme.copyWith(
|
||||||
|
backgroundColor: FocusFlowTokens.elevatedPanel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: child ?? const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts [date] into a local DateTime for Flutter's date picker.
|
||||||
|
DateTime _dateTimeForCivilDate(CivilDate date) {
|
||||||
|
return DateTime(date.year, date.month, date.day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts [dateTime] into a civil date without applying a timezone shift.
|
||||||
|
CivilDate _civilDateForDateTime(DateTime dateTime) {
|
||||||
|
return CivilDate(dateTime.year, dateTime.month, dateTime.day);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ class _TodayScreenScaffold extends StatefulWidget {
|
||||||
required this.onCompleteCard,
|
required this.onCompleteCard,
|
||||||
required this.onAddTask,
|
required this.onAddTask,
|
||||||
required this.onClearSelection,
|
required this.onClearSelection,
|
||||||
|
required this.onPreviousDate,
|
||||||
|
required this.onNextDate,
|
||||||
|
required this.onChooseDate,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Stores the `data` value for this object.
|
/// Stores the `data` value for this object.
|
||||||
|
|
@ -41,6 +44,18 @@ class _TodayScreenScaffold extends StatefulWidget {
|
||||||
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
final VoidCallback onClearSelection;
|
final VoidCallback onClearSelection;
|
||||||
|
|
||||||
|
/// Stores the `onPreviousDate` value for this object.
|
||||||
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
|
final VoidCallback onPreviousDate;
|
||||||
|
|
||||||
|
/// Stores the `onNextDate` value for this object.
|
||||||
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
|
final VoidCallback onNextDate;
|
||||||
|
|
||||||
|
/// Stores the `onChooseDate` value for this object.
|
||||||
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
|
final VoidCallback onChooseDate;
|
||||||
|
|
||||||
/// Creates the mutable state object used by this stateful widget.
|
/// Creates the mutable state object used by this stateful widget.
|
||||||
/// Flutter calls this once for each mounted widget instance before lifecycle callbacks and builds begin.
|
/// Flutter calls this once for each mounted widget instance before lifecycle callbacks and builds begin.
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@ part of '../focus_flow_app.dart';
|
||||||
/// Private implementation type for `_TodayScreenScaffoldState` in this library.
|
/// Private implementation type for `_TodayScreenScaffoldState` in this library.
|
||||||
/// It keeps supporting state and behavior scoped to the owning file so the public API stays focused on the scheduler concepts that callers use directly.
|
/// It keeps supporting state and behavior scoped to the owning file so the public API stays focused on the scheduler concepts that callers use directly.
|
||||||
class _TodayScreenScaffoldState extends State<_TodayScreenScaffold> {
|
class _TodayScreenScaffoldState extends State<_TodayScreenScaffold> {
|
||||||
|
/// Shared constant value for `_modalScrimTopInset`.
|
||||||
|
/// The constant gives this repeated scheduler or UI value one documented source of truth instead of scattering literals across call sites.
|
||||||
|
static const _modalScrimTopInset = 96.0;
|
||||||
|
|
||||||
/// Private state stored as `_timelineScrollTargetCardId` for this implementation.
|
/// Private state stored as `_timelineScrollTargetCardId` for this implementation.
|
||||||
/// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
|
/// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
|
||||||
String? _timelineScrollTargetCardId;
|
String? _timelineScrollTargetCardId;
|
||||||
|
|
@ -55,7 +59,12 @@ class _TodayScreenScaffoldState extends State<_TodayScreenScaffold> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
TopBar(dateLabel: widget.data.dateLabel),
|
TopBar(
|
||||||
|
dateLabel: widget.data.dateLabel,
|
||||||
|
onPreviousDate: widget.onPreviousDate,
|
||||||
|
onNextDate: widget.onNextDate,
|
||||||
|
onDatePressed: widget.onChooseDate,
|
||||||
|
),
|
||||||
if (widget.data.requiredBanner == null)
|
if (widget.data.requiredBanner == null)
|
||||||
const SizedBox(height: 16)
|
const SizedBox(height: 16)
|
||||||
else ...[
|
else ...[
|
||||||
|
|
@ -81,7 +90,11 @@ class _TodayScreenScaffoldState extends State<_TodayScreenScaffold> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.selectedCard != null) ...[
|
if (widget.selectedCard != null) ...[
|
||||||
Positioned.fill(
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
top: _modalScrimTopInset,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
key: const ValueKey('modal-click-away'),
|
key: const ValueKey('modal-click-away'),
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,26 @@ part 'top_bar/top_bar_settings_button.dart';
|
||||||
/// Header bar for the compact Today view.
|
/// Header bar for the compact Today view.
|
||||||
class TopBar extends StatelessWidget {
|
class TopBar extends StatelessWidget {
|
||||||
/// Creates a top bar with a display-ready [dateLabel].
|
/// Creates a top bar with a display-ready [dateLabel].
|
||||||
const TopBar({required this.dateLabel, super.key});
|
const TopBar({
|
||||||
|
required this.dateLabel,
|
||||||
|
this.onPreviousDate,
|
||||||
|
this.onNextDate,
|
||||||
|
this.onDatePressed,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
/// Date label shown beside the date navigation controls.
|
/// Date label shown beside the date navigation controls.
|
||||||
final String dateLabel;
|
final String dateLabel;
|
||||||
|
|
||||||
|
/// Callback invoked when the previous-day control is activated.
|
||||||
|
final VoidCallback? onPreviousDate;
|
||||||
|
|
||||||
|
/// Callback invoked when the next-day control is activated.
|
||||||
|
final VoidCallback? onNextDate;
|
||||||
|
|
||||||
|
/// Callback invoked when the choose-date control is activated.
|
||||||
|
final VoidCallback? onDatePressed;
|
||||||
|
|
||||||
/// Builds the widget subtree for this component.
|
/// Builds the widget subtree for this component.
|
||||||
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
||||||
@override
|
@override
|
||||||
|
|
@ -50,24 +65,53 @@ class TopBar extends StatelessWidget {
|
||||||
_IconButton(
|
_IconButton(
|
||||||
icon: Icons.calendar_today,
|
icon: Icons.calendar_today,
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
onPressed: () {},
|
tooltip: 'Choose date',
|
||||||
|
onPressed: onDatePressed,
|
||||||
),
|
),
|
||||||
_IconButton(
|
_IconButton(
|
||||||
icon: Icons.chevron_left,
|
icon: Icons.chevron_left,
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
onPressed: () {},
|
tooltip: 'Previous day',
|
||||||
|
onPressed: onPreviousDate,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: metrics.dateWidth,
|
width: metrics.dateWidth,
|
||||||
height: metrics.controlHeight,
|
height: metrics.controlHeight,
|
||||||
child: Center(
|
child: Tooltip(
|
||||||
child: FittedBox(
|
message: 'Choose date',
|
||||||
fit: BoxFit.scaleDown,
|
child: Semantics(
|
||||||
child: Text(
|
button: true,
|
||||||
dateLabel,
|
label: 'Choose date, $dateLabel',
|
||||||
style: TextStyle(
|
child: TextButton(
|
||||||
color: FocusFlowTokens.textPrimary,
|
key: const ValueKey('top-bar-date-button'),
|
||||||
fontSize: metrics.dateSize,
|
onPressed: onDatePressed,
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: FocusFlowTokens.textPrimary,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 6 * metrics.scale,
|
||||||
|
),
|
||||||
|
minimumSize: Size(
|
||||||
|
metrics.dateWidth,
|
||||||
|
metrics.controlHeight,
|
||||||
|
),
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
metrics.borderRadius,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
dateLabel,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.visible,
|
||||||
|
style: TextStyle(
|
||||||
|
color: FocusFlowTokens.textPrimary,
|
||||||
|
fontSize: metrics.dateSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -76,7 +120,8 @@ class TopBar extends StatelessWidget {
|
||||||
_IconButton(
|
_IconButton(
|
||||||
icon: Icons.chevron_right,
|
icon: Icons.chevron_right,
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
onPressed: () {},
|
tooltip: 'Next day',
|
||||||
|
onPressed: onNextDate,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
_ModeToggle(metrics: metrics),
|
_ModeToggle(metrics: metrics),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ class _IconButton extends StatelessWidget {
|
||||||
const _IconButton({
|
const _IconButton({
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.metrics,
|
required this.metrics,
|
||||||
|
required this.tooltip,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -22,9 +23,13 @@ class _IconButton extends StatelessWidget {
|
||||||
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
final _TopBarMetrics metrics;
|
final _TopBarMetrics metrics;
|
||||||
|
|
||||||
|
/// Stores the `tooltip` value for this object.
|
||||||
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
|
final String tooltip;
|
||||||
|
|
||||||
/// Stores the `onPressed` value for this object.
|
/// Stores the `onPressed` value for this object.
|
||||||
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
/// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior.
|
||||||
final VoidCallback onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
/// Builds the widget subtree for this component.
|
/// Builds the widget subtree for this component.
|
||||||
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
/// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data.
|
||||||
|
|
@ -32,6 +37,7 @@ class _IconButton extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
|
tooltip: tooltip,
|
||||||
constraints: BoxConstraints.tightFor(
|
constraints: BoxConstraints.tightFor(
|
||||||
width: metrics.iconButtonSize,
|
width: metrics.iconButtonSize,
|
||||||
height: metrics.iconButtonSize,
|
height: metrics.iconButtonSize,
|
||||||
|
|
|
||||||
80
apps/focus_flow_flutter/test/app/date_navigation_test.dart
Normal file
80
apps/focus_flow_flutter/test/app/date_navigation_test.dart
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 FocusFlow contributors
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
/// Tests app-level date navigation 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 date navigation tests.
|
||||||
|
void main() {
|
||||||
|
testWidgets('previous and next arrows reload selected dates', (tester) async {
|
||||||
|
await _pumpPlanApp(tester);
|
||||||
|
|
||||||
|
expect(find.text('May 20, 2025'), findsOneWidget);
|
||||||
|
expect(find.text('Clean coffee maker'), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.byTooltip('Next day'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('May 21, 2025'), findsOneWidget);
|
||||||
|
expect(find.text('Clean coffee maker'), findsNothing);
|
||||||
|
|
||||||
|
await tester.tap(find.byTooltip('Previous day'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('May 20, 2025'), findsOneWidget);
|
||||||
|
expect(find.text('Clean coffee maker'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('date picker jumps directly to the chosen date', (tester) async {
|
||||||
|
await _pumpPlanApp(tester);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const ValueKey('top-bar-date-button')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(DatePickerDialog), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text('15').last);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('OK'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('May 15, 2025'), findsOneWidget);
|
||||||
|
expect(find.text('Clean coffee maker'), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('open modal closes when changing dates', (tester) async {
|
||||||
|
await _pumpPlanApp(tester);
|
||||||
|
|
||||||
|
await tester.ensureVisible(find.byKey(const ValueKey('pay-bill')));
|
||||||
|
await tester.tap(find.byKey(const ValueKey('pay-bill')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byKey(const ValueKey('task-selection-modal')), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.byTooltip('Next day'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byKey(const ValueKey('task-selection-modal')), findsNothing);
|
||||||
|
expect(find.text('May 21, 2025'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 date navigation tests.
|
||||||
|
DemoSchedulerComposition _composition() {
|
||||||
|
return DemoSchedulerComposition.seeded(selectedDate: CivilDate(2025, 5, 20));
|
||||||
|
}
|
||||||
|
|
@ -418,6 +418,39 @@ void main() {
|
||||||
expect(tester.getSize(find.byType(OutlinedButton)).width, lessThan(136));
|
expect(tester.getSize(find.byType(OutlinedButton)).width, lessThan(136));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('top bar date controls invoke callbacks', (tester) async {
|
||||||
|
var previousCount = 0;
|
||||||
|
var nextCount = 0;
|
||||||
|
var datePressedCount = 0;
|
||||||
|
|
||||||
|
await _pumpConstrainedWidget(
|
||||||
|
tester,
|
||||||
|
size: const Size(760, 72),
|
||||||
|
child: TopBar(
|
||||||
|
dateLabel: 'June 30, 2026',
|
||||||
|
onPreviousDate: () {
|
||||||
|
previousCount += 1;
|
||||||
|
},
|
||||||
|
onNextDate: () {
|
||||||
|
nextCount += 1;
|
||||||
|
},
|
||||||
|
onDatePressed: () {
|
||||||
|
datePressedCount += 1;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byTooltip('Previous day'));
|
||||||
|
await tester.tap(find.byTooltip('Next day'));
|
||||||
|
await tester.tap(find.byKey(const ValueKey('top-bar-date-button')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(previousCount, 1);
|
||||||
|
expect(nextCount, 1);
|
||||||
|
expect(datePressedCount, 1);
|
||||||
|
expect(find.byTooltip('Choose date'), findsWidgets);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('required banner scales inside a compact header width', (
|
testWidgets('required banner scales inside a compact header width', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue