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 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/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..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,16 +42,30 @@ void main() { reason: 'getter should convert value'); }); - test('@convertJsMapProp works as expected', () { - final map = {'abc': true}; - final mapPropKey = Test().getPropKey((p) => p.mapProp); - - final setterResult = (Test()..mapProp = map)[mapPropKey]; - 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'); + group('@convertJsMapProp works as expected', () { + test('without generics', () { + final map = {'abc': true}; + final propKey = Test().getPropKey((p) => p.mapPropWithGenerics); + + final setterResult = (Test()..mapPropWithGenerics = map)[propKey]; + expect(setterResult, isA(), reason: 'setter should convert value'); + expect(unjsifyMapProp(setterResult as JsMap), map); + + 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; } 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`.'))); }); }); });