From 3cf2082e566452d1c63d78e7a17e8dcb5afd370f Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Mon, 15 Dec 2025 17:46:28 -0500 Subject: [PATCH 1/7] Fix DateTimeInput: reliable updates, localized display, and time-only support --- .../catalog/core_widgets/date_time_input.dart | 205 +++++++++++++--- .../core_widgets/date_time_input_test.dart | 230 +++++++++++++++--- 2 files changed, 368 insertions(+), 67 deletions(-) diff --git a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart index 56ab1f4f3..2c0b70c90 100644 --- a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart +++ b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart @@ -69,40 +69,24 @@ final dateTimeInput = CatalogItem( return ValueListenableBuilder( valueListenable: valueNotifier, builder: (context, value, child) { + final MaterialLocalizations localizations = MaterialLocalizations.of( + context, + ); + final String displayText = _getDisplayText( + value, + dateTimeInputData, + localizations, + ); + return ListTile( - title: Text(value ?? 'Select a date/time'), - onTap: () async { - final path = dateTimeInputData.value['path'] as String?; - if (path == null) { - return; - } - if (dateTimeInputData.enableDate) { - final DateTime? date = await showDatePicker( - context: itemContext.buildContext, - initialDate: DateTime.now(), - firstDate: DateTime(2000), - lastDate: DateTime(2100), - ); - if (date != null) { - itemContext.dataContext.update( - DataPath(path), - date.toIso8601String(), - ); - } - } - if (dateTimeInputData.enableTime) { - final TimeOfDay? time = await showTimePicker( - context: itemContext.buildContext, - initialTime: TimeOfDay.now(), - ); - if (time != null) { - itemContext.dataContext.update( - DataPath(path), - time.format(itemContext.buildContext), - ); - } - } - }, + key: Key(itemContext.id), + title: Text(displayText, key: Key('${itemContext.id}_text')), + onTap: () => _handleTap( + context: itemContext.buildContext, + dataContext: itemContext.dataContext, + data: dateTimeInputData, + value: value, + ), ); }, ); @@ -122,5 +106,160 @@ final dateTimeInput = CatalogItem( } ] ''', + () => ''' + [ + { + "id": "root", + "component": { + "DateTimeInput": { + "value": { + "path": "/myDate" + }, + "enableTime": false, + "outputFormat": "date_only" + } + } + } + ] + ''', + () => ''' + [ + { + "id": "root", + "component": { + "DateTimeInput": { + "value": { + "path": "/myTime" + }, + "enableDate": false, + "outputFormat": "time_only" + } + } + } + ] + ''', ], ); + +Future _handleTap({ + required BuildContext context, + required DataContext dataContext, + required _DateTimeInputData data, + required String? value, +}) async { + final path = data.value['path'] as String?; + if (path == null) { + return; + } + + final DateTime initialDate = + DateTime.tryParse(value ?? '') ?? + DateTime.tryParse('1970-01-01T$value') ?? + DateTime.now(); + + DateTime? newDate; + if (data.enableDate) { + newDate = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: DateTime(2000), + lastDate: DateTime(2100), + ); + if (newDate == null) return; // User cancelled. + } else { + newDate = initialDate; + } + + TimeOfDay? newTime; + if (data.enableTime) { + newTime = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(initialDate), + ); + if (newTime == null) { + // User cancelled. + return; + } + } else { + newTime = TimeOfDay.fromDateTime(initialDate); + } + + final finalDateTime = DateTime( + newDate.year, + newDate.month, + newDate.day, + data.enableTime ? newTime.hour : 0, + data.enableTime ? newTime.minute : 0, + ); + + String formattedValue; + final String? format = data.outputFormat; + + if (format == 'date_only' || (!data.enableTime && format == null)) { + formattedValue = finalDateTime.toIso8601String().split('T').first; + } else if (format == 'time_only' || (!data.enableDate && format == null)) { + final String hour = finalDateTime.hour.toString().padLeft(2, '0'); + final String minute = finalDateTime.minute.toString().padLeft(2, '0'); + formattedValue = '$hour:$minute:00'; + } else { + formattedValue = finalDateTime.toIso8601String(); + } + + dataContext.update(DataPath(path), formattedValue); +} + +String _getDisplayText( + String? value, + _DateTimeInputData data, + MaterialLocalizations localizations, +) { + String getPlaceholderText() { + if (data.enableDate && data.enableTime) { + return 'Select a date and time'; + } else if (data.enableDate) { + return 'Select a date'; + } else if (data.enableTime) { + return 'Select a time'; + } + return 'Select a date/time'; + } + + DateTime? tryParseDateOrTime(String value) { + return DateTime.tryParse(value) ?? DateTime.tryParse('1970-01-01T$value'); + } + + String formatDateTime(DateTime date) { + var datePart = ''; + var timePart = ''; + + if (data.enableDate) { + datePart = localizations.formatMediumDate(date); + } + + if (data.enableTime) { + timePart = localizations.formatTimeOfDay(TimeOfDay.fromDateTime(date)); + } + + if (data.enableDate && data.enableTime) { + return '$datePart $timePart'; + } else if (data.enableDate) { + return datePart; + } else if (data.enableTime) { + return timePart; + } + + // Fallback if neither is enabled (shouldn't happen with defaults). + return '$datePart $timePart'.trim(); + } + + if (value == null) { + return getPlaceholderText(); + } + + final DateTime? date = tryParseDateOrTime(value); + if (date == null) { + return value; + } + + return formatDateTime(date); +} diff --git a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart index 3bf2bc8a6..5d3025cb9 100644 --- a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart +++ b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart @@ -7,42 +7,171 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:genui/genui.dart'; void main() { - testWidgets('DateTimeInput widget renders and handles changes', ( - WidgetTester tester, - ) async { - final manager = A2uiMessageProcessor( - catalogs: [ - Catalog([ - CoreCatalogItems.dateTimeInput, - CoreCatalogItems.text, - ], catalogId: 'test_catalog'), - ], - ); - const surfaceId = 'testSurface'; - final components = [ - const Component( - id: 'datetime', - componentProperties: { - 'DateTimeInput': { - 'value': {'path': '/myDateTime'}, - }, - }, - ), - ]; - manager.handleMessage( - SurfaceUpdate(surfaceId: surfaceId, components: components), - ); - manager.handleMessage( - const BeginRendering( - surfaceId: surfaceId, - root: 'datetime', - catalogId: 'test_catalog', - ), - ); + testWidgets('renders and handles explicit updates', (tester) async { + final robot = DateTimeInputRobot(tester); + final (GenUiHost manager, String surfaceId) = setup('datetime', { + 'value': {'path': '/myDateTime'}, + 'enableTime': false, + }); + manager .dataModelForSurface(surfaceId) .update(DataPath('/myDateTime'), '2025-10-15'); + await robot.pumpSurface(manager, surfaceId); + + robot.expectInputText('datetime', 'Wed, Oct 15'); + }); + + testWidgets('displays correct placeholder/initial text based on mode', ( + tester, + ) async { + final robot = DateTimeInputRobot(tester); + + var (GenUiHost manager, String surfaceId) = setup('datetime_default', { + 'value': {'path': '/myDateTimeDefault'}, + }); + await robot.pumpSurface(manager, surfaceId); + robot.expectInputText('datetime_default', 'Select a date and time'); + + (manager, surfaceId) = setup('datetime_date_only', { + 'value': {'path': '/myDateOnly'}, + 'enableTime': false, + }); + await robot.pumpSurface(manager, surfaceId); + robot.expectInputText('datetime_date_only', 'Select a date'); + + (manager, surfaceId) = setup('datetime_time_only', { + 'value': {'path': '/myTimeOnly'}, + 'enableDate': false, + }); + await robot.pumpSurface(manager, surfaceId); + robot.expectInputText('datetime_time_only', 'Select a time'); + }); + + group('combined mode', () { + testWidgets('aborts update when time picker is cancelled', (tester) async { + final robot = DateTimeInputRobot(tester); + final (GenUiHost manager, String surfaceId) = setup('combined_mode', { + 'value': {'path': '/myDateTime'}, + }); + + manager + .dataModelForSurface(surfaceId) + .update(DataPath('/myDateTime'), '2022-01-01T14:30:00'); + + await robot.pumpSurface(manager, surfaceId); + + await robot.openPicker('combined_mode'); + await robot.selectDate('15'); + + robot.expectTimePickerVisible(); + await robot.cancelPicker(); + + final String? value = manager + .dataModelForSurface(surfaceId) + .getValue(DataPath('/myDateTime')); + expect(value, equals('2022-01-01T14:30:00')); + }); + }); + + group('time only mode', () { + testWidgets('aborts when time picker is cancelled', (tester) async { + final robot = DateTimeInputRobot(tester); + final (GenUiHost manager, String surfaceId) = setup('time_only_mode', { + 'value': {'path': '/myTime'}, + 'enableDate': false, + }); + + await robot.pumpSurface(manager, surfaceId); + + await robot.openPicker('time_only_mode'); + robot.expectTimePickerVisible(); + await robot.cancelPicker(); + + final String? value = manager + .dataModelForSurface(surfaceId) + .getValue(DataPath('/myTime')); + expect(value, isNull); + }); + + testWidgets('parses initial value correctly', (tester) async { + final robot = DateTimeInputRobot(tester); + final (GenUiHost manager, String surfaceId) = setup('time_only_parsing', { + 'value': {'path': '/myTimeProp'}, + 'enableDate': false, + }); + + manager + .dataModelForSurface(surfaceId) + .update(DataPath('/myTimeProp'), '14:32:00'); + + await robot.pumpSurface(manager, surfaceId); + + await robot.openPicker('time_only_parsing'); + + robot.expectPickerText('32'); + + await robot.cancelPicker(); + }); + }); + + group('date only mode', () { + testWidgets('updates immediately after date selection', (tester) async { + final robot = DateTimeInputRobot(tester); + final (GenUiHost manager, String surfaceId) = setup('date_only_mode', { + 'value': {'path': '/myDate'}, + 'enableTime': false, + }); + + await robot.pumpSurface(manager, surfaceId); + + await robot.openPicker('date_only_mode'); + await robot.selectDate('20'); + + final String? value = manager + .dataModelForSurface(surfaceId) + .getValue(DataPath('/myDate')); + expect(value, isNotNull); + expect(value, contains('2025-12-20')); + + robot.expectTimePickerHidden(); + }); + }); +} + +(GenUiHost, String) setup(String componentId, Map props) { + final catalog = Catalog([ + CoreCatalogItems.dateTimeInput, + ], catalogId: 'test_catalog'); + + final manager = A2uiMessageProcessor(catalogs: [catalog]); + const surfaceId = 'testSurface'; + + final components = [ + Component(id: componentId, componentProperties: {'DateTimeInput': props}), + ]; + + manager.handleMessage( + SurfaceUpdate(surfaceId: surfaceId, components: components), + ); + manager.handleMessage( + BeginRendering( + surfaceId: surfaceId, + root: componentId, + catalogId: 'test_catalog', + ), + ); + + return (manager, surfaceId); +} + +class DateTimeInputRobot { + final WidgetTester tester; + + DateTimeInputRobot(this.tester); + + Future pumpSurface(GenUiHost manager, String surfaceId) async { await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -50,7 +179,40 @@ void main() { ), ), ); + await tester.pumpAndSettle(); + } - expect(find.text('2025-10-15'), findsOneWidget); - }); + Future openPicker(String componentId) async { + await tester.tap(find.byKey(Key(componentId))); + await tester.pumpAndSettle(); + } + + Future selectDate(String day) async { + await tester.tap(find.text(day)); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + } + + Future cancelPicker() async { + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + } + + void expectInputText(String componentId, String text) { + final Finder finder = find.byKey(Key('${componentId}_text')); + expect(finder, findsOneWidget); + expect(tester.widget(finder).data, text); + } + + void expectPickerText(String text) { + expect(find.text(text), findsOneWidget); + } + + void expectTimePickerVisible() { + expect(find.text('Select time'), findsOneWidget); + } + + void expectTimePickerHidden() { + expect(find.text('Select time'), findsNothing); + } } From 3a9c89042a9967ae0052f3a4742fc31fc2126c30 Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Thu, 18 Dec 2025 15:53:07 -0500 Subject: [PATCH 2/7] fix test to not rely on December being the actual current month --- .../catalog/core_widgets/date_time_input_test.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart index 5d3025cb9..4962f9138 100644 --- a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart +++ b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart @@ -117,13 +117,18 @@ void main() { }); group('date only mode', () { - testWidgets('updates immediately after date selection', (tester) async { + testWidgets('updates immediately with date-only string after ' + 'date selection', (tester) async { final robot = DateTimeInputRobot(tester); final (GenUiHost manager, String surfaceId) = setup('date_only_mode', { 'value': {'path': '/myDate'}, 'enableTime': false, }); + manager + .dataModelForSurface(surfaceId) + .update(DataPath('/myDate'), '2022-01-01'); + await robot.pumpSurface(manager, surfaceId); await robot.openPicker('date_only_mode'); @@ -133,7 +138,8 @@ void main() { .dataModelForSurface(surfaceId) .getValue(DataPath('/myDate')); expect(value, isNotNull); - expect(value, contains('2025-12-20')); + // Verify that no time is included in the value. + expect(value, equals('2022-01-20')); robot.expectTimePickerHidden(); }); From 37e95077569f13c5c0091b749483f68c65003f21 Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Thu, 18 Dec 2025 16:06:16 -0500 Subject: [PATCH 3/7] add date range parameters; do some refactoring --- .../catalog/core_widgets/date_time_input.dart | 79 +++++++++---------- .../core_widgets/date_time_input_test.dart | 41 ++++++++++ 2 files changed, 79 insertions(+), 41 deletions(-) diff --git a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart index 2c0b70c90..83a59d1a7 100644 --- a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart +++ b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart @@ -19,6 +19,14 @@ final _schema = S.object( 'enableDate': S.boolean(), 'enableTime': S.boolean(), 'outputFormat': S.string(), + 'firstDate': S.string( + description: + 'The earliest selectable date (YYYY-MM-DD). Defaults to 1900-01-01.', + ), + 'lastDate': S.string( + description: + 'The latest selectable date (YYYY-MM-DD). Defaults to 2100-12-31.', + ), }, required: ['value'], ); @@ -29,17 +37,25 @@ extension type _DateTimeInputData.fromMap(JsonMap _json) { bool? enableDate, bool? enableTime, String? outputFormat, + String? firstDate, + String? lastDate, }) => _DateTimeInputData.fromMap({ 'value': value, 'enableDate': enableDate, 'enableTime': enableTime, 'outputFormat': outputFormat, + 'firstDate': firstDate, + 'lastDate': lastDate, }); JsonMap get value => _json['value'] as JsonMap; bool get enableDate => (_json['enableDate'] as bool?) ?? true; bool get enableTime => (_json['enableTime'] as bool?) ?? true; String? get outputFormat => _json['outputFormat'] as String?; + DateTime get firstDate => + DateTime.tryParse(_json['firstDate'] as String? ?? '') ?? DateTime(1900); + DateTime get lastDate => + DateTime.tryParse(_json['lastDate'] as String? ?? '') ?? DateTime(2100); } /// A catalog item representing a Material Design date and/or time input field. @@ -157,39 +173,35 @@ Future _handleTap({ DateTime.tryParse('1970-01-01T$value') ?? DateTime.now(); - DateTime? newDate; + DateTime resultDate = initialDate; + TimeOfDay resultTime = TimeOfDay.fromDateTime(initialDate); + if (data.enableDate) { - newDate = await showDatePicker( + final DateTime? pickedDate = await showDatePicker( context: context, initialDate: initialDate, - firstDate: DateTime(2000), - lastDate: DateTime(2100), + firstDate: data.firstDate, + lastDate: data.lastDate, ); - if (newDate == null) return; // User cancelled. - } else { - newDate = initialDate; + if (pickedDate == null) return; // User cancelled. + resultDate = pickedDate; } - TimeOfDay? newTime; if (data.enableTime) { - newTime = await showTimePicker( + final TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(initialDate), ); - if (newTime == null) { - // User cancelled. - return; - } - } else { - newTime = TimeOfDay.fromDateTime(initialDate); + if (pickedTime == null) return; // User cancelled. + resultTime = pickedTime; } final finalDateTime = DateTime( - newDate.year, - newDate.month, - newDate.day, - data.enableTime ? newTime.hour : 0, - data.enableTime ? newTime.minute : 0, + resultDate.year, + resultDate.month, + resultDate.day, + data.enableTime ? resultTime.hour : 0, + data.enableTime ? resultTime.minute : 0, ); String formattedValue; @@ -229,27 +241,12 @@ String _getDisplayText( } String formatDateTime(DateTime date) { - var datePart = ''; - var timePart = ''; - - if (data.enableDate) { - datePart = localizations.formatMediumDate(date); - } - - if (data.enableTime) { - timePart = localizations.formatTimeOfDay(TimeOfDay.fromDateTime(date)); - } - - if (data.enableDate && data.enableTime) { - return '$datePart $timePart'; - } else if (data.enableDate) { - return datePart; - } else if (data.enableTime) { - return timePart; - } - - // Fallback if neither is enabled (shouldn't happen with defaults). - return '$datePart $timePart'.trim(); + final parts = [ + if (data.enableDate) localizations.formatMediumDate(date), + if (data.enableTime) + localizations.formatTimeOfDay(TimeOfDay.fromDateTime(date)), + ]; + return parts.join(' '); } if (value == null) { diff --git a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart index 4962f9138..874d33e9d 100644 --- a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart +++ b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart @@ -144,6 +144,47 @@ void main() { robot.expectTimePickerHidden(); }); }); + + group('date range configuration', () { + testWidgets('respects custom firstDate and lastDate', (tester) async { + final robot = DateTimeInputRobot(tester); + final (GenUiHost manager, String surfaceId) = setup('custom_range', { + 'value': {'path': '/myDate'}, + 'firstDate': '2020-01-01', + 'lastDate': '2030-12-31', + }); + + await robot.pumpSurface(manager, surfaceId); + + await robot.openPicker('custom_range'); + + final DatePickerDialog dialog = tester.widget( + find.byType(DatePickerDialog), + ); + expect(dialog.firstDate, DateTime(2020)); + expect(dialog.lastDate, DateTime(2030, 12, 31)); + + await robot.cancelPicker(); + }); + + testWidgets('defaults to 1900-2100 when not specified', (tester) async { + final robot = DateTimeInputRobot(tester); + final (GenUiHost manager, String surfaceId) = setup('default_range', { + 'value': {'path': '/myDate'}, + }); + + await robot.pumpSurface(manager, surfaceId); + await robot.openPicker('default_range'); + + final DatePickerDialog dialog = tester.widget( + find.byType(DatePickerDialog), + ); + expect(dialog.firstDate, DateTime(1900)); + expect(dialog.lastDate, DateTime(2100)); + + await robot.cancelPicker(); + }); + }); } (GenUiHost, String) setup(String componentId, Map props) { From f8769de5f0bafd0ea4e9a5fda9bd5b9adc9e115b Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Thu, 18 Dec 2025 16:16:18 -0500 Subject: [PATCH 4/7] expand selectable date range --- .../lib/src/catalog/core_widgets/date_time_input.dart | 9 +++++---- .../test/catalog/core_widgets/date_time_input_test.dart | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart index 83a59d1a7..9d4ad62dc 100644 --- a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart +++ b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart @@ -21,11 +21,11 @@ final _schema = S.object( 'outputFormat': S.string(), 'firstDate': S.string( description: - 'The earliest selectable date (YYYY-MM-DD). Defaults to 1900-01-01.', + 'The earliest selectable date (YYYY-MM-DD). Defaults to -9999-01-01.', ), 'lastDate': S.string( description: - 'The latest selectable date (YYYY-MM-DD). Defaults to 2100-12-31.', + 'The latest selectable date (YYYY-MM-DD). Defaults to 9999-12-31.', ), }, required: ['value'], @@ -53,9 +53,10 @@ extension type _DateTimeInputData.fromMap(JsonMap _json) { bool get enableTime => (_json['enableTime'] as bool?) ?? true; String? get outputFormat => _json['outputFormat'] as String?; DateTime get firstDate => - DateTime.tryParse(_json['firstDate'] as String? ?? '') ?? DateTime(1900); + DateTime.tryParse(_json['firstDate'] as String? ?? '') ?? DateTime(-9999); DateTime get lastDate => - DateTime.tryParse(_json['lastDate'] as String? ?? '') ?? DateTime(2100); + DateTime.tryParse(_json['lastDate'] as String? ?? '') ?? + DateTime(9999, 12, 31); } /// A catalog item representing a Material Design date and/or time input field. diff --git a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart index 874d33e9d..83860826d 100644 --- a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart +++ b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart @@ -167,7 +167,7 @@ void main() { await robot.cancelPicker(); }); - testWidgets('defaults to 1900-2100 when not specified', (tester) async { + testWidgets('defaults to -9999 to 9999 when not specified', (tester) async { final robot = DateTimeInputRobot(tester); final (GenUiHost manager, String surfaceId) = setup('default_range', { 'value': {'path': '/myDate'}, @@ -179,8 +179,8 @@ void main() { final DatePickerDialog dialog = tester.widget( find.byType(DatePickerDialog), ); - expect(dialog.firstDate, DateTime(1900)); - expect(dialog.lastDate, DateTime(2100)); + expect(dialog.firstDate, DateTime(-9999)); + expect(dialog.lastDate, DateTime(9999, 12, 31)); await robot.cancelPicker(); }); From c7114d99b975b8aa8100f285036c8ace340b61c0 Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Thu, 18 Dec 2025 16:25:10 -0500 Subject: [PATCH 5/7] use full date format for displaying value to user --- .../lib/src/catalog/core_widgets/date_time_input.dart | 2 +- .../test/catalog/core_widgets/date_time_input_test.dart | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart index 9d4ad62dc..d08942e59 100644 --- a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart +++ b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart @@ -243,7 +243,7 @@ String _getDisplayText( String formatDateTime(DateTime date) { final parts = [ - if (data.enableDate) localizations.formatMediumDate(date), + if (data.enableDate) localizations.formatFullDate(date), if (data.enableTime) localizations.formatTimeOfDay(TimeOfDay.fromDateTime(date)), ]; diff --git a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart index 83860826d..2cfe0fd08 100644 --- a/packages/genui/test/catalog/core_widgets/date_time_input_test.dart +++ b/packages/genui/test/catalog/core_widgets/date_time_input_test.dart @@ -20,7 +20,7 @@ void main() { await robot.pumpSurface(manager, surfaceId); - robot.expectInputText('datetime', 'Wed, Oct 15'); + robot.expectInputText('datetime', 'Wednesday, October 15, 2025'); }); testWidgets('displays correct placeholder/initial text based on mode', ( @@ -140,6 +140,7 @@ void main() { expect(value, isNotNull); // Verify that no time is included in the value. expect(value, equals('2022-01-20')); + robot.expectInputText('date_only_mode', 'Thursday, January 20, 2022'); robot.expectTimePickerHidden(); }); @@ -248,7 +249,11 @@ class DateTimeInputRobot { void expectInputText(String componentId, String text) { final Finder finder = find.byKey(Key('${componentId}_text')); expect(finder, findsOneWidget); - expect(tester.widget(finder).data, text); + final String actualText = tester.widget(finder).data!; + if (actualText != text) { + print('EXPECTATION FAILED: Expected "$text", found "$actualText"'); + } + expect(actualText, text); } void expectPickerText(String text) { From 9ffb24076767321bdee2ae7e4350c1bd1027c85a Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Thu, 18 Dec 2025 16:34:53 -0500 Subject: [PATCH 6/7] delete `outputFormat` parameter --- .../catalog/core_widgets/date_time_input.dart | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart index d08942e59..8bcf75325 100644 --- a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart +++ b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart @@ -18,7 +18,6 @@ final _schema = S.object( ), 'enableDate': S.boolean(), 'enableTime': S.boolean(), - 'outputFormat': S.string(), 'firstDate': S.string( description: 'The earliest selectable date (YYYY-MM-DD). Defaults to -9999-01-01.', @@ -36,14 +35,12 @@ extension type _DateTimeInputData.fromMap(JsonMap _json) { required JsonMap value, bool? enableDate, bool? enableTime, - String? outputFormat, String? firstDate, String? lastDate, }) => _DateTimeInputData.fromMap({ 'value': value, 'enableDate': enableDate, 'enableTime': enableTime, - 'outputFormat': outputFormat, 'firstDate': firstDate, 'lastDate': lastDate, }); @@ -51,7 +48,6 @@ extension type _DateTimeInputData.fromMap(JsonMap _json) { JsonMap get value => _json['value'] as JsonMap; bool get enableDate => (_json['enableDate'] as bool?) ?? true; bool get enableTime => (_json['enableTime'] as bool?) ?? true; - String? get outputFormat => _json['outputFormat'] as String?; DateTime get firstDate => DateTime.tryParse(_json['firstDate'] as String? ?? '') ?? DateTime(-9999); DateTime get lastDate => @@ -132,8 +128,7 @@ final dateTimeInput = CatalogItem( "value": { "path": "/myDate" }, - "enableTime": false, - "outputFormat": "date_only" + "enableTime": false } } } @@ -148,8 +143,7 @@ final dateTimeInput = CatalogItem( "value": { "path": "/myTime" }, - "enableDate": false, - "outputFormat": "time_only" + "enableDate": false } } } @@ -206,15 +200,16 @@ Future _handleTap({ ); String formattedValue; - final String? format = data.outputFormat; - if (format == 'date_only' || (!data.enableTime && format == null)) { + if (data.enableDate && !data.enableTime) { formattedValue = finalDateTime.toIso8601String().split('T').first; - } else if (format == 'time_only' || (!data.enableDate && format == null)) { + } else if (!data.enableDate && data.enableTime) { final String hour = finalDateTime.hour.toString().padLeft(2, '0'); final String minute = finalDateTime.minute.toString().padLeft(2, '0'); formattedValue = '$hour:$minute:00'; } else { + // Both enabled (or both disabled, which shouldn't happen), + // write full ISO string. formattedValue = finalDateTime.toIso8601String(); } From 995ddb2f020d97f36d30e7f500306610065d779c Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Thu, 18 Dec 2025 16:37:28 -0500 Subject: [PATCH 7/7] lints --- .../genui/lib/src/catalog/core_widgets/date_time_input.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart index 8bcf75325..7cb7c838d 100644 --- a/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart +++ b/packages/genui/lib/src/catalog/core_widgets/date_time_input.dart @@ -168,8 +168,8 @@ Future _handleTap({ DateTime.tryParse('1970-01-01T$value') ?? DateTime.now(); - DateTime resultDate = initialDate; - TimeOfDay resultTime = TimeOfDay.fromDateTime(initialDate); + var resultDate = initialDate; + var resultTime = TimeOfDay.fromDateTime(initialDate); if (data.enableDate) { final DateTime? pickedDate = await showDatePicker( @@ -237,7 +237,7 @@ String _getDisplayText( } String formatDateTime(DateTime date) { - final parts = [ + final List parts = [ if (data.enableDate) localizations.formatFullDate(date), if (data.enableTime) localizations.formatTimeOfDay(TimeOfDay.fromDateTime(date)),