From 4be50420852efccc923421e361a5220cc744a09b Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 18 Dec 2025 12:04:56 -0700 Subject: [PATCH 1/4] Add regression test for convertJsMapProp with generics --- .../prop_conversion_annotations_test.dart | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/prop_conversion_annotations_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/prop_conversion_annotations_test.dart index c2ce62ae8..ae8c055fa 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/prop_conversion_annotations_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/prop_conversion_annotations_test.dart @@ -43,15 +43,29 @@ void main() { }); test('@convertJsMapProp works as expected', () { - final map = {'abc': true}; - final mapPropKey = Test().getPropKey((p) => p.mapProp); + test('without generics', () { + final map = {'abc': true}; + final propKey = Test().getPropKey((p) => p.mapPropWithGenerics); - final setterResult = (Test()..mapProp = map)[mapPropKey]; - expect(setterResult, isA(), reason: 'setter should convert value'); - expect(unjsifyMapProp(setterResult as JsMap), map); + final setterResult = (Test()..mapPropWithGenerics = map)[propKey]; + expect(setterResult, isA(), reason: 'setter should convert value'); + expect(unjsifyMapProp(setterResult as JsMap), map); - expect(Test({mapPropKey: jsifyMapProp(map)}).mapProp, map, - reason: 'getter should convert value'); + expect(Test({propKey: jsifyMapProp(map)}).mapPropWithGenerics, map, + reason: 'getter should convert value'); + }); + + test('with generics', () { + final map = {'abc': true}; + final propKey = Test().getPropKey((p) => p.mapProp); + + final setterResult = (Test()..mapProp = map)[propKey]; + expect(setterResult, isA(), reason: 'setter should convert value'); + expect(unjsifyMapProp(setterResult as JsMap), map); + + expect(Test({propKey: jsifyMapProp(map)}).mapProp, map, + reason: 'getter should convert value'); + }); }); test('@convertJsRefProp works as expected', () { @@ -89,6 +103,9 @@ mixin TestProps on UiProps { @convertJsMapProp Map? mapProp; + @convertJsMapProp + Map? mapPropWithGenerics; + @convertJsRefProp dynamic refProp; } From fed7ddb33024769cdb7618fca407b3d9acb9ac6c Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 18 Dec 2025 12:08:49 -0700 Subject: [PATCH 2/4] Allow generics in Map type for @convertJsMapProp props --- lib/src/builder/codegen/accessors_generator.dart | 13 ++++++++++--- test/vm_tests/builder/codegen_test.dart | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/src/builder/codegen/accessors_generator.dart b/lib/src/builder/codegen/accessors_generator.dart index 2951cc3f7..2da7cc412 100644 --- a/lib/src/builder/codegen/accessors_generator.dart +++ b/lib/src/builder/codegen/accessors_generator.dart @@ -469,15 +469,22 @@ PropConversionConfig? parseConversionConfig({ final convertJsRefProp = getConstantAnnotation(field, 'convertJsRefProp', annotations.convertJsRefProp); if (convertJsMapProp != null) { + const allowedConvertedTypes = { + 'Map?', + 'Map?', + }; conversionConfig = PropConversionConfig( rawType: 'JsMap?', convertedType: 'Map?', setter: 'jsifyMapProp', getter: 'unjsifyMapProp', ); - if (conversionConfig.convertedType != typeSource) { + assert(allowedConvertedTypes.contains(conversionConfig.convertedType)); + if (!allowedConvertedTypes.contains(typeSource)) { handleAnnotationError( - 'A prop annotated with `@convertJsMapProp` should be typed as `Map?`.', field); + 'A prop annotated with `@convertJsMapProp` must be typed as one of: ' + '${allowedConvertedTypes.map((t) => '`$t`').join(', ')}', + field); return null; } } else if (convertJsRefProp != null) { @@ -489,7 +496,7 @@ PropConversionConfig? parseConversionConfig({ ); if (conversionConfig.convertedType != typeSource) { handleAnnotationError( - 'A prop annotated with `@convertJsRefProp` should be typed as `dynamic`.', field); + 'A prop annotated with `@convertJsRefProp` must be typed as `dynamic`.', field); return null; } } diff --git a/test/vm_tests/builder/codegen_test.dart b/test/vm_tests/builder/codegen_test.dart index 7ec6c50df..e958bc9f7 100644 --- a/test/vm_tests/builder/codegen_test.dart +++ b/test/vm_tests/builder/codegen_test.dart @@ -1200,7 +1200,7 @@ main() { late String foo;'''; setUpAndGenerate(OverReactSrc.abstractProps(backwardsCompatible: false, body: body).source); - verify(() => logger.severe(contains('Unsupported prop annotation combination for prop \'foo\': A prop annotated with `@convertJsMapProp` should be typed as `Map?`.'))); + verify(() => logger.severe(contains('Unsupported prop annotation combination for prop \'foo\': A prop annotated with `@convertJsMapProp` must be typed as one of: `Map?`, `Map?`'))); }); test('@convertJsRefProp of the wrong prop type', () { @@ -1209,7 +1209,7 @@ main() { Map? foo;'''; setUpAndGenerate(OverReactSrc.abstractProps(backwardsCompatible: false, body: body).source); - verify(() => logger.severe(contains('Unsupported prop annotation combination for prop \'foo\': A prop annotated with `@convertJsRefProp` should be typed as `dynamic`.'))); + verify(() => logger.severe(contains('Unsupported prop annotation combination for prop \'foo\': A prop annotated with `@convertJsRefProp` must be typed as `dynamic`.'))); }); }); }); From 3e031088eafeca293849aa553959ea5c2ba98809 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Thu, 18 Dec 2025 12:10:43 -0700 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c81bc3654..4eb44ec39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - [#989] Optimize generated code to decrease dart2js compile size, saving ~577 bytes per component (when using `-03 --csp --minify`) - [#992] Fix compilation errors for legacy boilerplate defined in libraries with a Dart language version of >=3.0 +- [#993] Allow `@convertJsMapProp` props to be typed as `Map?`, not just `Map?` ## 5.4.6 - [#986] Set up gha-dart-oss From 602f19e5690277516bc39ccb94782b1d3bd217c0 Mon Sep 17 00:00:00 2001 From: Greg Littlefield Date: Fri, 19 Dec 2025 15:26:00 -0700 Subject: [PATCH 4/4] Fix nested test typo --- .../new_boilerplate/prop_conversion_annotations_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/prop_conversion_annotations_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/prop_conversion_annotations_test.dart index ae8c055fa..3d603d300 100644 --- a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/prop_conversion_annotations_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/prop_conversion_annotations_test.dart @@ -42,7 +42,7 @@ void main() { reason: 'getter should convert value'); }); - test('@convertJsMapProp works as expected', () { + group('@convertJsMapProp works as expected', () { test('without generics', () { final map = {'abc': true}; final propKey = Test().getPropKey((p) => p.mapPropWithGenerics);