diff --git a/lib/src/builder/codegen/accessors_generator.dart b/lib/src/builder/codegen/accessors_generator.dart index fa59acaf2..dd477e526 100644 --- a/lib/src/builder/codegen/accessors_generator.dart +++ b/lib/src/builder/codegen/accessors_generator.dart @@ -99,6 +99,7 @@ abstract class TypedMapAccessorsGenerator extends BoilerplateDeclarationGenerato } generatedClass.write(_generateAccessors()); + generatedClass.writeln('}'); generatedClass.writeln(); return generatedClass.toString(); @@ -149,6 +150,8 @@ abstract class TypedMapAccessorsGenerator extends BoilerplateDeclarationGenerato StringBuffer output = StringBuffer(); + final requiredPropChecks = []; + node.members.whereType().where((field) => !field.isStatic).forEach((field) { T? getConstantAnnotation(AnnotatedNode member, String name, T value) { return member.metadata.any((annotation) => annotation.name.name == name) ? value : null; @@ -158,6 +161,8 @@ abstract class TypedMapAccessorsGenerator extends BoilerplateDeclarationGenerato final requiredProp = getConstantAnnotation(field, 'requiredProp', annotations.requiredProp); final nullableRequiredProp = getConstantAnnotation(field, 'nullableRequiredProp', annotations.nullableRequiredProp); + final disableRequiredPropValidation = getConstantAnnotation( + field, 'disableRequiredPropValidation', annotations.disableRequiredPropValidation); if (accessorMeta?.doNotGenerate == true) { return; @@ -198,6 +203,12 @@ abstract class TypedMapAccessorsGenerator extends BoilerplateDeclarationGenerato annotationCount++; isRequired = true; isPotentiallyNullable = true; + + if (type.isProps && disableRequiredPropValidation == null) { + requiredPropChecks.add(' if(!props.containsKey($keyValue)) {\n' + ' throw MissingRequiredPropsError(${stringLiteral('Required prop `$accessorName` is missing.')});\n' + '}\n'); + } } if (accessorMeta != null) { @@ -352,6 +363,18 @@ abstract class TypedMapAccessorsGenerator extends BoilerplateDeclarationGenerato output.write(staticVariablesImpl); + if (type.isProps && + version != Version.v3_legacyDart2Only && + version != Version.v2_legacyBackwardsCompat) { + final validateRequiredPropsMethod = '\n @override\n' + ' @mustCallSuper\n' + ' void validateRequiredProps() {\n' + ' super.validateRequiredProps();\n' + ' ${requiredPropChecks.join('\n')}\n' + ' }\n'; + output.write(validateRequiredPropsMethod); + } + return output.toString(); } } diff --git a/lib/src/component/abstract_transition_props.over_react.g.dart b/lib/src/component/abstract_transition_props.over_react.g.dart index 574a83397..79c3b82c3 100644 --- a/lib/src/component/abstract_transition_props.over_react.g.dart +++ b/lib/src/component/abstract_transition_props.over_react.g.dart @@ -103,6 +103,12 @@ mixin $TransitionPropsMixin on TransitionPropsMixin { _$key__onWillShow__TransitionPropsMixin, _$key__onDidShow__TransitionPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/lib/src/component/error_boundary.over_react.g.dart b/lib/src/component/error_boundary.over_react.g.dart index 4e812a885..87a668f92 100644 --- a/lib/src/component/error_boundary.over_react.g.dart +++ b/lib/src/component/error_boundary.over_react.g.dart @@ -349,6 +349,12 @@ mixin $ErrorBoundaryProps on ErrorBoundaryProps { _$key__shouldLogErrors__ErrorBoundaryProps, _$key__logger__ErrorBoundaryProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/lib/src/component/resize_sensor.over_react.g.dart b/lib/src/component/resize_sensor.over_react.g.dart index 1365e936f..9989cfe76 100644 --- a/lib/src/component/resize_sensor.over_react.g.dart +++ b/lib/src/component/resize_sensor.over_react.g.dart @@ -266,6 +266,12 @@ mixin $ResizeSensorProps on ResizeSensorProps { _$key__onDetachedMountCheck__ResizeSensorProps, _$key__onDidReset__ResizeSensorProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/lib/src/component/suspense_component.over_react.g.dart b/lib/src/component/suspense_component.over_react.g.dart index 2f50f22c0..41d57775f 100644 --- a/lib/src/component/suspense_component.over_react.g.dart +++ b/lib/src/component/suspense_component.over_react.g.dart @@ -28,6 +28,12 @@ mixin $SuspensePropsMixin on SuspensePropsMixin { _$prop__fallback__SuspensePropsMixin ]; static const List $propKeys = [_$key__fallback__SuspensePropsMixin]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/lib/src/component/with_transition.over_react.g.dart b/lib/src/component/with_transition.over_react.g.dart index f9fa2b7b3..c1f416f13 100644 --- a/lib/src/component/with_transition.over_react.g.dart +++ b/lib/src/component/with_transition.over_react.g.dart @@ -300,6 +300,12 @@ mixin $WithTransitionPropsMixin on WithTransitionPropsMixin { _$key__childPropsByPhase__WithTransitionPropsMixin, _$key__transitionTimeout__WithTransitionPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/lib/src/component_declaration/annotations.dart b/lib/src/component_declaration/annotations.dart index c854df485..0bf139f90 100644 --- a/lib/src/component_declaration/annotations.dart +++ b/lib/src/component_declaration/annotations.dart @@ -15,6 +15,9 @@ // Dummy annotations that would be used by Pub code generator library over_react.component_declaration.annotations; +// Exported for use in generated code. +export 'package:meta/meta.dart' show mustCallSuper; + /// Annotation used with the `over_react` builder to declare a `UiFactory` for a component. /// /// @Factory() @@ -274,7 +277,7 @@ class AbstractComponent2 implements AbstractComponent { // ignore: deprecated_me /// /// Classes using this annotation must include the abstract `props` getter. /// -/// __Deprecated.__ Use the `@Props()` annotation instead if you need to make use of an annotation argument. +/// __Deprecated.__ Use the `@Props()` annotation instead if you need to make use of an annotation argument. /// Otherwise, this can be removed completely. Will be removed in the 4.0.0 release of over_react. @Deprecated('Use the @Props() annotation if you need to make use of an annotation argument. Otherwise, this can be removed completely. Will be removed in the 4.0.0 release of over_react.') class PropsMixin implements TypedMap { @@ -298,7 +301,7 @@ class PropsMixin implements TypedMap { /// /// Classes using this annotation must include the abstract `state` getter. /// -/// __Deprecated.__ Use the `@State()` annotation instead if you need to make use of an annotation argument. +/// __Deprecated.__ Use the `@State()` annotation instead if you need to make use of an annotation argument. /// Otherwise, this can be removed completely. Will be removed in the 4.0.0 release of over_react. @Deprecated('Use the @State() annotation if you need to make use of an annotation argument. Otherwise, this can be removed completely. Will be removed in the 4.0.0 release of over_react.') class StateMixin implements TypedMap { @@ -381,3 +384,10 @@ class Accessor { abstract class TypedMap { String? get keyNamespace; } + +/// Prevents required prop validation from being performed on a prop. +const _DisableRequiredPropValidation disableRequiredPropValidation = _DisableRequiredPropValidation(); + +class _DisableRequiredPropValidation { + const _DisableRequiredPropValidation(); +} diff --git a/lib/src/component_declaration/builder_helpers.dart b/lib/src/component_declaration/builder_helpers.dart index 105766ea4..a461110d5 100644 --- a/lib/src/component_declaration/builder_helpers.dart +++ b/lib/src/component_declaration/builder_helpers.dart @@ -14,7 +14,6 @@ library over_react.component_declaration.builder_helpers; -import 'package:meta/meta.dart'; import '../../over_react.dart'; import './component_base.dart' as component_base; import './annotations.dart' as annotations; @@ -131,63 +130,6 @@ abstract class UiProps extends component_base.UiProps with GeneratedClass { /// This can be used to derive consumed props by usage in conjunction with [addUnconsumedProps] /// and [addUnconsumedDomProps]. @toBeGenerated PropsMetaCollection get staticMeta => throw UngeneratedError(member: #meta); - - @override - @visibleForOverriding - @mustCallSuper - void validateRequiredProps() { - super.validateRequiredProps(); - // This fails when staticMeta isn't generated, so return early for now so tests don't fail. - // FIXME(null-safety) generate a static implementation of this instead in FED-1886, and remove this - return; - - // ignore: dead_code - List? missingRequiredProps; - List? nullNonNullableRequiredProps; - - for (final meta in staticMeta.all) { - for (final prop in meta.props) { - if (prop.isRequired) { - if (prop.isNullable) { - if (!props.containsKey(prop.key)) { - (missingRequiredProps ??= []).add(prop); - } - } else { - // Avoid looking up the key twice. - if (props[prop.key] == null) { - if (props.containsKey(prop.key)) { - (nullNonNullableRequiredProps ??= []).add(prop); - } else { - (missingRequiredProps ??= []).add(prop); - } - } - } - } - } - } - - if (missingRequiredProps == null && nullNonNullableRequiredProps == null) { - return; - } - - String formatPropKey(String propKey) => '`$propKey`'; - - final messageSegments = []; - if (missingRequiredProps != null) { - messageSegments.add('Required props are missing: ${missingRequiredProps.map((prop) { - var propMessage = formatPropKey(prop.key); - if (prop.isNullable) propMessage += ' (can be null, but must be specified)'; - return propMessage; - }).join(' ,')}.'); - } - if (nullNonNullableRequiredProps != null) { - messageSegments - .add('Required non-nullable props are null: ${nullNonNullableRequiredProps.map((prop) { - return formatPropKey(prop.key); - }).join(' ,')}.'); - } - throw MissingRequiredPropsError(messageSegments.join(' ')); - } } class MissingRequiredPropsError extends Error { diff --git a/lib/src/component_declaration/component_base.dart b/lib/src/component_declaration/component_base.dart index 70c9131b8..ca795ed7c 100644 --- a/lib/src/component_declaration/component_base.dart +++ b/lib/src/component_declaration/component_base.dart @@ -621,9 +621,10 @@ abstract class UiProps extends MapBase assert(_validateChildren(childArguments.length == 1 ? childArguments.single : childArguments)); - // FIXME(null-safety) finalize this implementation and add escape-hatch to opt out in FED-1886 assert(() { - validateRequiredProps(); + if (_shouldValidateRequiredProps) { + validateRequiredProps(); + } return true; }()); @@ -676,10 +677,22 @@ abstract class UiProps extends MapBase : const {}; } - // FIXME(null-safety) document and generate overrides in FED-1886 + /// Validate at run-time that all required props are set. + /// + /// This method is overridden in generated files. @visibleForOverriding @mustCallSuper void validateRequiredProps() {} + + /// Whether [validateRequiredProps] should be run. + var _shouldValidateRequiredProps = true; + + /// Prevents [validateRequiredProps] from being called. + /// + /// Allows validation to be skipped to support cases where required props are cloned onto an element. + void disableRequiredPropValidation() { + _shouldValidateRequiredProps = false; + } } /// A class that declares the `_map` getter shared by [PropsMapViewMixin]/[StateMapViewMixin] and [MapViewMixin]. diff --git a/lib/src/component_declaration/flux_component.over_react.g.dart b/lib/src/component_declaration/flux_component.over_react.g.dart index 30ed6c60f..2335d1ed3 100644 --- a/lib/src/component_declaration/flux_component.over_react.g.dart +++ b/lib/src/component_declaration/flux_component.over_react.g.dart @@ -49,6 +49,19 @@ mixin $FluxUiPropsMixin _$key__actions__FluxUiPropsMixin, _$key__store__FluxUiPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + if (!props.containsKey('FluxUiPropsMixin.actions')) { + throw MissingRequiredPropsError('Required prop `actions` is missing.'); + } + + if (!props.containsKey('FluxUiPropsMixin.store')) { + throw MissingRequiredPropsError('Required prop `store` is missing.'); + } + } } @Deprecated('This API is for use only within generated code.' diff --git a/lib/src/over_react_redux/over_react_redux.dart b/lib/src/over_react_redux/over_react_redux.dart index 73171b180..a37dc79a0 100644 --- a/lib/src/over_react_redux/over_react_redux.dart +++ b/lib/src/over_react_redux/over_react_redux.dart @@ -21,6 +21,7 @@ import 'package:meta/meta.dart'; import 'package:over_react/component_base.dart'; import 'package:over_react/src/component_declaration/annotations.dart'; import 'package:over_react/src/component_declaration/builder_helpers.dart' as builder_helpers; +import 'package:over_react/src/component_declaration/builder_helpers.dart' show MissingRequiredPropsError; import 'package:over_react/src/component_declaration/component_type_checking.dart'; import 'package:over_react/src/component_declaration/function_component.dart'; import 'package:over_react/src/util/context.dart'; @@ -51,7 +52,9 @@ abstract class _$ConnectPropsMixin implements UiProps { @override Map get props; - dynamic Function(dynamic action)? dispatch; + // Disable validation since this prop is set by the `connect` HOC, and does not need to be set by consumers. + @disableRequiredPropValidation + late dynamic Function(dynamic action) dispatch; } // ignore: prefer_generic_function_type_aliases diff --git a/lib/src/over_react_redux/over_react_redux.over_react.g.dart b/lib/src/over_react_redux/over_react_redux.over_react.g.dart index 6f7b6c092..176e50943 100644 --- a/lib/src/over_react_redux/over_react_redux.over_react.g.dart +++ b/lib/src/over_react_redux/over_react_redux.over_react.g.dart @@ -15,17 +15,20 @@ abstract class ConnectPropsMixin implements _$ConnectPropsMixin { /// @override - dynamic Function(dynamic action)? get dispatch => + @disableRequiredPropValidation + dynamic Function(dynamic action) get dispatch => (props[_$key__dispatch___$ConnectPropsMixin] ?? null) as dynamic Function( - dynamic action)?; + dynamic action); /// @override - set dispatch(dynamic Function(dynamic action)? value) => + @disableRequiredPropValidation + set dispatch(dynamic Function(dynamic action) value) => props[_$key__dispatch___$ConnectPropsMixin] = value; /* GENERATED CONSTANTS */ static const PropDescriptor _$prop__dispatch___$ConnectPropsMixin = - PropDescriptor(_$key__dispatch___$ConnectPropsMixin); + PropDescriptor(_$key__dispatch___$ConnectPropsMixin, + isRequired: true, isNullable: true); static const String _$key__dispatch___$ConnectPropsMixin = 'dispatch'; static const List $props = [ @@ -74,6 +77,15 @@ mixin $ReduxProviderPropsMixin on ReduxProviderPropsMixin { _$key__store__ReduxProviderPropsMixin, _$key__context__ReduxProviderPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + if (!props.containsKey('store')) { + throw MissingRequiredPropsError('Required prop `store` is missing.'); + } + } } @Deprecated('This API is for use only within generated code.' diff --git a/lib/src/util/react_util.dart b/lib/src/util/react_util.dart index 43b39f483..f60c98402 100644 --- a/lib/src/util/react_util.dart +++ b/lib/src/util/react_util.dart @@ -101,4 +101,8 @@ class UiPropsMapView extends MapView @override void validateRequiredProps() => throw UnimplementedError('@PropsMixin instances do not implement validateRequiredProps'); + + @override + void disableRequiredPropValidation() => + throw UnimplementedError('@PropsMixin instances do not implement disableRequiredPropValidation'); } diff --git a/lib/src/util/safe_render_manager/safe_render_manager_helper.over_react.g.dart b/lib/src/util/safe_render_manager/safe_render_manager_helper.over_react.g.dart index 39a13b1bf..9e624a134 100644 --- a/lib/src/util/safe_render_manager/safe_render_manager_helper.over_react.g.dart +++ b/lib/src/util/safe_render_manager/safe_render_manager_helper.over_react.g.dart @@ -289,6 +289,12 @@ mixin $SafeRenderManagerHelperProps on SafeRenderManagerHelperProps { _$key__getInitialContent__SafeRenderManagerHelperProps, _$key__contentRef__SafeRenderManagerHelperProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/component/abstract_transition_test.over_react.g.dart b/test/over_react/component/abstract_transition_test.over_react.g.dart index 44e0968cd..7869444cf 100644 --- a/test/over_react/component/abstract_transition_test.over_react.g.dart +++ b/test/over_react/component/abstract_transition_test.over_react.g.dart @@ -391,6 +391,12 @@ mixin $TransitionerPropsMixin on TransitionerPropsMixin { _$key__initiallyShown__TransitionerPropsMixin, _$key__transitionTimeout__TransitionerPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/component/element_type_test.over_react.g.dart b/test/over_react/component/element_type_test.over_react.g.dart index 9b83c3272..a6a86f0d1 100644 --- a/test/over_react/component/element_type_test.over_react.g.dart +++ b/test/over_react/component/element_type_test.over_react.g.dart @@ -164,6 +164,12 @@ mixin $CustomTestProps on CustomTestProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -183,6 +189,12 @@ mixin $CustomFnTestProps on CustomFnTestProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/component/error_boundary/shared_stack_tests.over_react.g.dart b/test/over_react/component/error_boundary/shared_stack_tests.over_react.g.dart index 9bdc30900..3e534e8f5 100644 --- a/test/over_react/component/error_boundary/shared_stack_tests.over_react.g.dart +++ b/test/over_react/component/error_boundary/shared_stack_tests.over_react.g.dart @@ -256,6 +256,12 @@ mixin $ThrowingComponent2Props on ThrowingComponent2Props { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -275,6 +281,12 @@ mixin $ThrowingFunctionComponentProps on ThrowingFunctionComponentProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -294,6 +306,12 @@ mixin $ThrowingForwardRefComponentProps on ThrowingForwardRefComponentProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/component/fixtures/lazy_load_me_props.over_react.g.dart b/test/over_react/component/fixtures/lazy_load_me_props.over_react.g.dart index c5453e13a..1d0b7caca 100644 --- a/test/over_react/component/fixtures/lazy_load_me_props.over_react.g.dart +++ b/test/over_react/component/fixtures/lazy_load_me_props.over_react.g.dart @@ -31,6 +31,12 @@ mixin $LazyLoadMePropsMixin on LazyLoadMePropsMixin { static const List $propKeys = [ _$key__initialCount__LazyLoadMePropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/component/fixtures/pure_test_components.over_react.g.dart b/test/over_react/component/fixtures/pure_test_components.over_react.g.dart index 01d99c285..220bdafa5 100644 --- a/test/over_react/component/fixtures/pure_test_components.over_react.g.dart +++ b/test/over_react/component/fixtures/pure_test_components.over_react.g.dart @@ -434,6 +434,12 @@ mixin $PureTestPropsMixin on PureTestPropsMixin { _$key__childBoolProp__PureTestPropsMixin, _$key__childFuncProp__PureTestPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -480,6 +486,12 @@ mixin $SharedPureTestPropsMixin on SharedPureTestPropsMixin { _$key__sharedBoolProp__SharedPureTestPropsMixin, _$key__someVDomEl__SharedPureTestPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/component/memo_test.over_react.g.dart b/test/over_react/component/memo_test.over_react.g.dart index 3000a8dff..5b66b3fa2 100644 --- a/test/over_react/component/memo_test.over_react.g.dart +++ b/test/over_react/component/memo_test.over_react.g.dart @@ -209,6 +209,12 @@ mixin $FunctionCustomPropsProps on FunctionCustomPropsProps { _$key__testProp__FunctionCustomPropsProps, _$key__testFuncProp__FunctionCustomPropsProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/component/ref_util_test.over_react.g.dart b/test/over_react/component/ref_util_test.over_react.g.dart index a28607f1f..b96ac8066 100644 --- a/test/over_react/component/ref_util_test.over_react.g.dart +++ b/test/over_react/component/ref_util_test.over_react.g.dart @@ -170,6 +170,12 @@ mixin $BasicProps on BasicProps { static const List $props = [_$prop__childId__BasicProps]; static const List $propKeys = [_$key__childId__BasicProps]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -189,6 +195,12 @@ mixin $BasicUiFunctionProps on BasicUiFunctionProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/component_declaration/builder_integration_tests/backwards_compatible/required_accessor_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/backwards_compatible/required_accessor_integration_test.dart index b1174c015..5ab2dbe99 100644 --- a/test/over_react/component_declaration/builder_integration_tests/backwards_compatible/required_accessor_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/backwards_compatible/required_accessor_integration_test.dart @@ -29,7 +29,10 @@ void main() { group('(backwards compatible with Dart 1) properly identifies required props by', () { group('throwing when a prop is required and not set', () { test('on mount', () { - expect(() => render(ComponentTest()..nullable = true), + expect(() => render(ComponentTest() + ..nullable = true + ..lateProp = true + )(), throwsPropError_Required('ComponentTestProps.required', 'This Prop is Required for testing purposes.') ); }); @@ -39,6 +42,7 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect(() => react_dom.render((ComponentTest()..nullable = true)(), mountNode), @@ -52,6 +56,7 @@ void main() { expect(() => render(ComponentTest() ..required = null ..nullable = true + ..lateProp = true ), throwsPropError_Required('ComponentTestProps.required')); }); @@ -60,12 +65,14 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect( () => react_dom.render((ComponentTest() ..required = null ..nullable = true + ..lateProp = true )(), mountNode), throwsPropError_Required('ComponentTestProps.required', 'This Prop is Required for testing purposes.') ); @@ -74,7 +81,10 @@ void main() { group('throwing when a prop is nullable and not set', () { test('on mount', () { - expect(() => render(ComponentTest()..required = true), + expect(() => render(ComponentTest() + ..required = true + ..lateProp = true + )(), throwsPropError_Required('ComponentTestProps.nullable')); }); @@ -83,6 +93,7 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect(() => react_dom.render((ComponentTest()..required = true)(), mountNode), @@ -96,6 +107,7 @@ void main() { expect(() => render(ComponentTest() ..nullable = true ..required = true + ..lateProp = true ), returnsNormally); }); @@ -104,11 +116,13 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect(() => react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode), returnsNormally); }); }); @@ -118,6 +132,7 @@ void main() { expect(() => render(ComponentTest() ..nullable = null ..required = true + ..lateProp = true ), returnsNormally); }); @@ -126,14 +141,35 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect(() => react_dom.render((ComponentTest() ..required = true ..nullable = null + ..lateProp = true )(), mountNode), returnsNormally); }); }); + + group('for late props', () { + test('does not throw on invocation', () { + expect(() { + (ComponentTest() + ..required = true + ..nullable = true + )(); + }, + returnsNormally); + }); + + test('on mount', () { + expect(() => render(ComponentTest() + ..nullable = null + ..required = true + ), throwsPropError_Required('ComponentTestProps.lateProp')); + }); + }); }); } @@ -148,6 +184,8 @@ class _$ComponentTestProps extends UiProps { @Accessor(isRequired: true, isNullable: true, requiredErrorMessage: 'This prop can be set to null!') dynamic nullable; + + late bool lateProp; } @Component() diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart index 6b98d57f8..6d6eb8b0b 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.dart @@ -26,7 +26,11 @@ void main() { test('on mount', () { expect(() { mount( - (ComponentTest()..nullable = true..requiredAndLengthLimited = [1,2])(), + (ComponentTest() + ..nullable = true + ..requiredAndLengthLimited = [1,2] + ..lateProp = true + )(), attachedToDocument: true, ); }, logsPropRequiredError('ComponentTestProps.required', 'This Prop is Required for testing purposes.')); @@ -41,6 +45,7 @@ void main() { ..required = true ..nullable = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )(), attachedToDocument: true ); @@ -50,6 +55,7 @@ void main() { jacket.rerender((ComponentTest() ..nullable = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )() ); }, logsPropRequiredError('ComponentTestProps.required', 'This Prop is Required for testing purposes.')); @@ -64,6 +70,7 @@ void main() { ..required = null ..nullable = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )()); }, logsPropRequiredError('ComponentTestProps.required', 'This Prop is Required for testing purposes.')); }); @@ -77,6 +84,7 @@ void main() { ..required = true ..nullable = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )(), attachedToDocument: true); }, logsNoPropTypeWarnings); @@ -86,6 +94,7 @@ void main() { ..required = null ..nullable = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )()); }, logsPropRequiredError('ComponentTestProps.required', 'This Prop is Required for testing purposes.')); }); @@ -97,6 +106,7 @@ void main() { render((ComponentTest() ..required = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )()); }, logsPropRequiredError('ComponentTestProps.nullable', 'This prop can be set to null!')); }); @@ -110,6 +120,7 @@ void main() { ..required = true ..nullable = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )(), attachedToDocument: true); }, logsNoPropTypeWarnings); @@ -118,6 +129,7 @@ void main() { jacket.rerender((ComponentTest() ..required = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )()); }, logsPropRequiredError('ComponentTestProps.nullable', 'This prop can be set to null!')); }); @@ -130,6 +142,7 @@ void main() { ..nullable = true ..required = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )()); }, logsNoPropTypeWarnings); }); @@ -139,6 +152,7 @@ void main() { ..required = true ..nullable = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )(), attachedToDocument: true, ); @@ -148,6 +162,7 @@ void main() { ..required = true ..nullable = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )()); }, logsNoPropTypeWarnings); }); @@ -160,6 +175,7 @@ void main() { ..nullable = null ..requiredAndLengthLimited = [1,2] ..required = true + ..lateProp = true )()); }, logsNoPropTypeWarnings); }); @@ -173,6 +189,7 @@ void main() { ..required = true ..nullable = true ..requiredAndLengthLimited = [1,2] + ..lateProp = true )(), attachedToDocument: true); }, logsNoPropTypeWarnings); @@ -182,6 +199,7 @@ void main() { ..required = true ..nullable = null ..requiredAndLengthLimited = [1,2] + ..lateProp = true )()); }, logsNoPropTypeWarnings); }); @@ -193,6 +211,7 @@ void main() { mount((ComponentTest() ..nullable = null ..required = true + ..lateProp = true )()); }, logsPropValueError('null', 'ComponentTestProps.requiredAndLengthLimited')); }); @@ -203,10 +222,32 @@ void main() { ..required = true ..nullable = true ..requiredAndLengthLimited = [1] + ..lateProp = true )()); }, logsPropValueError('1', 'ComponentTestProps.requiredAndLengthLimited')); }); }); + + group('for late props', () { + test('does not throw on invocation', () { + expect(() { + (ComponentTest() + ..required = true + ..nullable = true + ..requiredAndLengthLimited = [1,2] + )(); + }, + returnsNormally); + }); + + test('on mount', () { + expect(() => render(ComponentTest() + ..nullable = null + ..required = true + ..requiredAndLengthLimited = [1,2] + ), logsPropRequiredError('ComponentTestProps.lateProp')); + }); + }); }); } @@ -225,6 +266,7 @@ class _$ComponentTestProps extends UiProps { @Accessor(isRequired: true, isNullable: false, requiredErrorMessage: 'This Prop Array is Required for testing purposes.') List? requiredAndLengthLimited; + late bool lateProp; } @Component2() diff --git a/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.over_react.g.dart index 70e4d2f18..4cd5cab76 100644 --- a/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/component2/required_accessor_integration_test.over_react.g.dart @@ -75,6 +75,16 @@ abstract class _$ComponentTestPropsAccessorsMixin requiredErrorMessage: 'This Prop Array is Required for testing purposes.') set requiredAndLengthLimited(List? value) => props[_$key__requiredAndLengthLimited___$ComponentTestProps] = value; + + /// + @override + bool get lateProp => + (props[_$key__lateProp___$ComponentTestProps] ?? null) as bool; + + /// + @override + set lateProp(bool value) => + props[_$key__lateProp___$ComponentTestProps] = value; /* GENERATED CONSTANTS */ static const PropDescriptor _$prop__required___$ComponentTestProps = PropDescriptor(_$key__required___$ComponentTestProps, @@ -90,22 +100,29 @@ abstract class _$ComponentTestPropsAccessorsMixin _$key__requiredAndLengthLimited___$ComponentTestProps, isRequired: true, errorMessage: 'This Prop Array is Required for testing purposes.'); + static const PropDescriptor _$prop__lateProp___$ComponentTestProps = + PropDescriptor(_$key__lateProp___$ComponentTestProps, + isRequired: true, isNullable: true); static const String _$key__required___$ComponentTestProps = 'ComponentTestProps.required'; static const String _$key__nullable___$ComponentTestProps = 'ComponentTestProps.nullable'; static const String _$key__requiredAndLengthLimited___$ComponentTestProps = 'ComponentTestProps.requiredAndLengthLimited'; + static const String _$key__lateProp___$ComponentTestProps = + 'ComponentTestProps.lateProp'; static const List $props = [ _$prop__required___$ComponentTestProps, _$prop__nullable___$ComponentTestProps, - _$prop__requiredAndLengthLimited___$ComponentTestProps + _$prop__requiredAndLengthLimited___$ComponentTestProps, + _$prop__lateProp___$ComponentTestProps ]; static const List $propKeys = [ _$key__required___$ComponentTestProps, _$key__nullable___$ComponentTestProps, - _$key__requiredAndLengthLimited___$ComponentTestProps + _$key__requiredAndLengthLimited___$ComponentTestProps, + _$key__lateProp___$ComponentTestProps ]; } diff --git a/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/null_safety_validate_required_props_test.dart b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/null_safety_validate_required_props_test.dart new file mode 100644 index 000000000..178db25b5 --- /dev/null +++ b/test/over_react/component_declaration/builder_integration_tests/new_boilerplate/null_safety_validate_required_props_test.dart @@ -0,0 +1,265 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:over_react/over_react.dart'; +import 'package:react_testing_library/react_testing_library.dart' as rtl; +import 'package:test/test.dart'; + +import '../../../../test_util/test_util.dart'; + +part 'null_safety_validate_required_props_test.over_react.g.dart'; + +void main() { + group('(New boilerplate) validates required props:', () { + group('non-nullable required prop', () { + group('throws when a prop is required and not set', () { + test('on invocation', () { + expect(() { + (ComponentTest() + ..requiredNullable = true + )(); + }, + throwsA(isA().having( + (e) => e.toString(), + 'toString value', + contains( + 'Required prop `requiredNonNullable` is missing.')))); + }); + + test('on mount', () { + expect(() { + rtl.render((ComponentTest() + ..requiredNullable = true + )()); + }, + throwsA(isA().having( + (e) => e.toString(), + 'toString value', + contains( + 'Required prop `requiredNonNullable` is missing.')))); + }); + + test('on re-render', () { + late rtl.RenderResult view; + + expect(() { + view = rtl.render((ComponentTest() + ..requiredNonNullable = true + ..requiredNullable = true + )()); + }, returnsNormally); + + expect(() { + view.rerender((ComponentTest() + ..requiredNullable = true + )()); + }, + throwsA(isA().having( + (e) => e.toString(), + 'toString value', + contains( + 'Required prop `requiredNonNullable` is missing.')))); + }); + }); + + test('does not throw when the prop is set', () { + expect(() { + rtl.render((ComponentTest() + ..requiredNonNullable = true + ..requiredNullable = true + )()); + }, returnsNormally); + }); + }); + + group('nullable required prop', () { + group('throws when a prop is required and not set', () { + test('on invocation', () { + expect(() { + (ComponentTest() + ..requiredNonNullable = true + )(); + }, + throwsA(isA().having( + (e) => e.toString(), + 'toString value', + contains('Required prop `requiredNullable` is missing.')))); + }); + + test('on mount', () { + expect(() { + rtl.render((ComponentTest() + ..requiredNonNullable = true + )()); + }, + throwsA(isA().having( + (e) => e.toString(), + 'toString value', + contains('Required prop `requiredNullable` is missing.')))); + }); + + test('on re-render', () { + late rtl.RenderResult view; + + expect(() { + view = rtl.render((ComponentTest() + ..requiredNonNullable = true + ..requiredNullable = true + )()); + }, returnsNormally); + + expect(() { + view.rerender((ComponentTest() + ..requiredNonNullable = true + )()); + }, + throwsA(isA().having( + (e) => e.toString(), + 'toString value', + contains('Required prop `requiredNullable` is missing.')))); + }); + }); + + test('does not throw when the prop is set to null', () { + expect(() { + rtl.render((ComponentTest() + ..requiredNonNullable = true + ..requiredNullable = null + )()); + }, returnsNormally); + }); + + test('does not throw when the prop is set', () { + expect(() { + rtl.render((ComponentTest() + ..requiredNonNullable = true + ..requiredNullable = true + )()); + }, returnsNormally); + }); + }); + + test('@disableRequiredPropValidation annotation turns off validation for specific props', () { + expect(() { + rtl.render((ComponentTest() + ..requiredNonNullable = true + ..requiredNullable = true + )()); + }, allOf(returnsNormally, + logsPropRequiredError('ComponentTestProps.disabledRequiredProp'), + logsPropRequiredError('ComponentTestProps.disabledNullableRequiredProp'), + logsPropRequiredError('ComponentTestProps.ref'), + )); + }); + + test('disableRequiredPropValidation method turns off validation for component usage', () { + expect(() { + rtl.render((ComponentTest() + ..disableRequiredPropValidation() + )()); + }, allOf(returnsNormally, + logsPropRequiredError('ComponentTestProps.disabledRequiredProp'), + logsPropRequiredError('ComponentTestProps.disabledNullableRequiredProp'), + logsPropRequiredError('ComponentTestProps.ref'), + logsPropRequiredError('ComponentTestProps.requiredNonNullable'), + logsPropRequiredError('ComponentTestProps.requiredNullable'), + )); + }); + + group('required props in multiple mixins', () { + test('throw an error when a prop in the first mixin is missing', () { + expect(() { + (MultipleMixinsTest() + ..requiredNullable = true + ..secondRequiredProp = true + )(); + }, + throwsA(isA().having( + (e) => e.toString(), + 'toString value', + contains( + 'Required prop `requiredNonNullable` is missing.')))); + }); + + test('throw an error when a prop in the second mixin is missing', () { + expect(() { + (MultipleMixinsTest() + ..requiredNullable = true + ..requiredNonNullable = true + )(); + }, + throwsA(isA().having( + (e) => e.toString(), + 'toString value', + contains( + 'Required prop `secondRequiredProp` is missing.')))); + }); + + test('does not throw when all required props are set', () { + expect(() { + (MultipleMixinsTest() + ..requiredNullable = true + ..requiredNonNullable = true + ..secondRequiredProp = true + )(); + }, + returnsNormally); + }); + }); + }, tags: 'ddc'); + + test('(New boilerplate) validates required props: does not throw in dart2js', () { + expect(() { + rtl.render((ComponentTest())()); + }, + returnsNormally); + }, tags: 'no-ddc'); +} + +// ignore: undefined_identifier, invalid_assignment +UiFactory ComponentTest = _$ComponentTest; + +mixin ComponentTestProps on UiProps { + late bool requiredNonNullable; + + late bool? requiredNullable; + + @disableRequiredPropValidation + late bool disabledRequiredProp; + + @disableRequiredPropValidation + late bool? disabledNullableRequiredProp; + + @disableRequiredPropValidation + @override + late dynamic ref; + + bool? nullable; +} + +class ComponentTestComponent extends UiComponent2 { + @override + render() => Dom.div()(); +} + +UiFactory MultipleMixinsTest = uiFunction( + (props) {}, + _$MultipleMixinsTestConfig, // ignore: undefined_identifier +); + +mixin MultipleMixinsTestPropsMixin on UiProps { + late bool secondRequiredProp; +} + +class MultipleMixinsTestProps = UiProps with MultipleMixinsTestPropsMixin, ComponentTestProps; diff --git a/test/over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.dart b/test/over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.dart index 604042443..7be292874 100644 --- a/test/over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.dart +++ b/test/over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.dart @@ -29,7 +29,10 @@ void main() { group('properly identifies required props by', () { group('throwing when a prop is required and not set', () { test('on mount', () { - expect(() => render(ComponentTest()..nullable = true), + expect(() => render(ComponentTest() + ..nullable = true + ..lateProp = true + )(), throwsPropError_Required('ComponentTestProps.required', 'This Prop is Required for testing purposes.') ); }); @@ -39,6 +42,7 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect(() => react_dom.render((ComponentTest()..nullable = true)(), mountNode), @@ -52,6 +56,7 @@ void main() { expect(() => render(ComponentTest() ..required = null ..nullable = true + ..lateProp = true ), throwsPropError_Required('ComponentTestProps.required')); }); @@ -60,12 +65,14 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect( () => react_dom.render((ComponentTest() ..required = null ..nullable = true + ..lateProp = true )(), mountNode), throwsPropError_Required('ComponentTestProps.required', 'This Prop is Required for testing purposes.') ); @@ -74,7 +81,10 @@ void main() { group('throwing when a prop is nullable and not set', () { test('on mount', () { - expect(() => render(ComponentTest()..required = true), + expect(() => render(ComponentTest() + ..required = true + ..lateProp = true + )(), throwsPropError_Required('ComponentTestProps.nullable')); }); @@ -83,6 +93,7 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect(() => react_dom.render((ComponentTest()..required = true)(), mountNode), @@ -96,6 +107,7 @@ void main() { expect(() => render(ComponentTest() ..nullable = true ..required = true + ..lateProp = true ), returnsNormally); }); @@ -104,11 +116,13 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect(() => react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode), returnsNormally); }); }); @@ -118,6 +132,7 @@ void main() { expect(() => render(ComponentTest() ..nullable = null ..required = true + ..lateProp = true ), returnsNormally); }); @@ -126,14 +141,35 @@ void main() { react_dom.render((ComponentTest() ..required = true ..nullable = true + ..lateProp = true )(), mountNode); expect(() => react_dom.render((ComponentTest() ..required = true ..nullable = null + ..lateProp = true )(), mountNode), returnsNormally); }); }); + + group('for late props', () { + test('does not throw on invocation', () { + expect(() { + (ComponentTest() + ..required = true + ..nullable = true + )(); + }, + returnsNormally); + }); + + test('on mount', () { + expect(() => render(ComponentTest() + ..nullable = null + ..required = true + ), throwsPropError_Required('ComponentTestProps.lateProp')); + }); + }); }); } @@ -148,6 +184,8 @@ class _$ComponentTestProps extends UiProps { @Accessor(isRequired: true, isNullable: true, requiredErrorMessage: 'This prop can be set to null!') dynamic nullable; + + late bool lateProp; } @Component() diff --git a/test/over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.over_react.g.dart b/test/over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.over_react.g.dart index 122354f87..4db52d13b 100644 --- a/test/over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.over_react.g.dart +++ b/test/over_react/component_declaration/builder_integration_tests/required_accessor_integration_test.over_react.g.dart @@ -56,6 +56,16 @@ abstract class _$ComponentTestPropsAccessorsMixin requiredErrorMessage: 'This prop can be set to null!') set nullable(dynamic value) => props[_$key__nullable___$ComponentTestProps] = value; + + /// + @override + bool get lateProp => + (props[_$key__lateProp___$ComponentTestProps] ?? null) as bool; + + /// + @override + set lateProp(bool value) => + props[_$key__lateProp___$ComponentTestProps] = value; /* GENERATED CONSTANTS */ static const PropDescriptor _$prop__required___$ComponentTestProps = PropDescriptor(_$key__required___$ComponentTestProps, @@ -66,18 +76,25 @@ abstract class _$ComponentTestPropsAccessorsMixin isRequired: true, isNullable: true, errorMessage: 'This prop can be set to null!'); + static const PropDescriptor _$prop__lateProp___$ComponentTestProps = + PropDescriptor(_$key__lateProp___$ComponentTestProps, + isRequired: true, isNullable: true); static const String _$key__required___$ComponentTestProps = 'ComponentTestProps.required'; static const String _$key__nullable___$ComponentTestProps = 'ComponentTestProps.nullable'; + static const String _$key__lateProp___$ComponentTestProps = + 'ComponentTestProps.lateProp'; static const List $props = [ _$prop__required___$ComponentTestProps, - _$prop__nullable___$ComponentTestProps + _$prop__nullable___$ComponentTestProps, + _$prop__lateProp___$ComponentTestProps ]; static const List $propKeys = [ _$key__required___$ComponentTestProps, - _$key__nullable___$ComponentTestProps + _$key__nullable___$ComponentTestProps, + _$key__lateProp___$ComponentTestProps ]; } diff --git a/test/over_react/component_declaration/function_type_checking_test/components.over_react.g.dart b/test/over_react/component_declaration/function_type_checking_test/components.over_react.g.dart index 98809424f..24a9bc772 100644 --- a/test/over_react/component_declaration/function_type_checking_test/components.over_react.g.dart +++ b/test/over_react/component_declaration/function_type_checking_test/components.over_react.g.dart @@ -187,6 +187,12 @@ mixin $TestAProps on TestAProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -206,6 +212,12 @@ mixin $TestBProps on TestBProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -225,6 +237,12 @@ mixin $TestParentProps on TestParentProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -244,6 +262,12 @@ mixin $TestSubtypeProps on TestSubtypeProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -263,6 +287,12 @@ mixin $TestSubsubtypeProps on TestSubsubtypeProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -282,6 +312,12 @@ mixin $TestExtendtypeProps on TestExtendtypeProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -301,6 +337,12 @@ mixin $OneLevelWrapperProps on OneLevelWrapperProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -320,6 +362,12 @@ mixin $TwoLevelWrapperProps on TwoLevelWrapperProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -341,6 +389,12 @@ mixin $DoNotReferenceThisFactoryExceptForInASingleTestProps static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -361,6 +415,12 @@ mixin $TestUninitializedParentProps on TestUninitializedParentProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/util/cast_ui_factory_test.over_react.g.dart b/test/over_react/util/cast_ui_factory_test.over_react.g.dart index 4bf0ee2e9..93b63579f 100644 --- a/test/over_react/util/cast_ui_factory_test.over_react.g.dart +++ b/test/over_react/util/cast_ui_factory_test.over_react.g.dart @@ -163,6 +163,12 @@ mixin $BasicProps on BasicProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/util/component_debug_name_test.over_react.g.dart b/test/over_react/util/component_debug_name_test.over_react.g.dart index bc42e9e92..693b724c2 100644 --- a/test/over_react/util/component_debug_name_test.over_react.g.dart +++ b/test/over_react/util/component_debug_name_test.over_react.g.dart @@ -250,6 +250,12 @@ mixin $TestComponent2Props on TestComponent2Props { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/util/js_component_test.over_react.g.dart b/test/over_react/util/js_component_test.over_react.g.dart index 3d3584f3e..885c8894f 100644 --- a/test/over_react/util/js_component_test.over_react.g.dart +++ b/test/over_react/util/js_component_test.over_react.g.dart @@ -70,6 +70,12 @@ mixin $TestPropsMixin on TestPropsMixin { _$key__dynamicProp__TestPropsMixin, _$key__untypedProp__TestPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -113,6 +119,12 @@ mixin $ASecondPropsMixin on ASecondPropsMixin { _$key__disabled__ASecondPropsMixin, _$key__anotherProp__ASecondPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react/util/prop_conversion_test.over_react.g.dart b/test/over_react/util/prop_conversion_test.over_react.g.dart index 7a41a05f3..36ba43c65 100644 --- a/test/over_react/util/prop_conversion_test.over_react.g.dart +++ b/test/over_react/util/prop_conversion_test.over_react.g.dart @@ -179,6 +179,12 @@ mixin $ExpectsDartMapPropProps on ExpectsDartMapPropProps { static const List $propKeys = [ _$key__dartMapProp__ExpectsDartMapPropProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -198,6 +204,12 @@ mixin $ExpectsDartStylePropProps on ExpectsDartStylePropProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -217,6 +229,12 @@ mixin $ExpectsListChildrenPropProps on ExpectsListChildrenPropProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -236,6 +254,12 @@ mixin $ClassComponentProps on ClassComponentProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -255,6 +279,12 @@ mixin $BasicForwardRefProps on BasicForwardRefProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -289,6 +319,12 @@ mixin $DartTestJsWrapperPropsMixin on DartTestJsWrapperPropsMixin { static const List $propKeys = [ _$key__onRender__DartTestJsWrapperPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -396,6 +432,12 @@ mixin $TestJsProps on TestJsProps { _$key__inputComponent__TestJsProps, _$key__buttonComponent__TestJsProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react_component_declaration_test.dart b/test/over_react_component_declaration_test.dart index d2c8924a6..60860dcb9 100644 --- a/test/over_react_component_declaration_test.dart +++ b/test/over_react_component_declaration_test.dart @@ -65,6 +65,7 @@ import 'over_react/component_declaration/builder_integration_tests/new_boilerpla import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_test.dart' as new_boilerplate_component_integration_test; import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/component_integration_verbose_syntax_test.dart' as new_boilerplate_component_integration_verbose_syntax_test; import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/constant_required_accessor_integration_test.dart' as new_boilerplate_constant_required_accessor_integration_test; +import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/null_safety_validate_required_props_test.dart' as new_boilerplate_null_safety_validate_required_props_test; import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/covariant_accessor_override_integration_test.dart' as new_boilerplate_covariant_accessor_override_integration_test; import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/do_not_generate_accessor_integration_test.dart' as new_boilerplate_do_not_generate_accessor_integration_test; import 'over_react/component_declaration/builder_integration_tests/new_boilerplate/function_component_test.dart' as new_boilerplate_function_component_integration_test; @@ -136,4 +137,5 @@ main() { new_boilerplate_required_accessor_integration_test.main(); new_boilerplate_stateful_component_integration_test.main(); new_boilerplate_unassigned_prop_integration_test.main(); + new_boilerplate_null_safety_validate_required_props_test.main(); } diff --git a/test/over_react_component_declaration_test.html b/test/over_react_component_declaration_test.html index 14154933b..3cdbb0e12 100644 --- a/test/over_react_component_declaration_test.html +++ b/test/over_react_component_declaration_test.html @@ -21,6 +21,7 @@ + diff --git a/test/over_react_redux/fixtures/connect_flux_counter.dart b/test/over_react_redux/fixtures/connect_flux_counter.dart index a7467e137..92d2c3fa9 100644 --- a/test/over_react_redux/fixtures/connect_flux_counter.dart +++ b/test/over_react_redux/fixtures/connect_flux_counter.dart @@ -36,7 +36,7 @@ class _$ConnectFluxCounterProps extends UiProps { void Function()? mutateStoreDirectly; - late FluxActions actions; + FluxActions? actions; } @Component2() @@ -57,7 +57,7 @@ class ConnectFluxCounterComponent } else if (props.increment != null) { props.increment!(); } else { - props.actions.incrementAction(); + props.actions?.incrementAction(); } } )('+'), diff --git a/test/over_react_redux/fixtures/connect_flux_counter.over_react.g.dart b/test/over_react_redux/fixtures/connect_flux_counter.over_react.g.dart index 3d1c923d4..16df6e0e5 100644 --- a/test/over_react_redux/fixtures/connect_flux_counter.over_react.g.dart +++ b/test/over_react_redux/fixtures/connect_flux_counter.over_react.g.dart @@ -90,12 +90,13 @@ abstract class _$ConnectFluxCounterPropsAccessorsMixin /// @override - FluxActions get actions => - (props[_$key__actions___$ConnectFluxCounterProps] ?? null) as FluxActions; + FluxActions? get actions => + (props[_$key__actions___$ConnectFluxCounterProps] ?? null) + as FluxActions?; /// @override - set actions(FluxActions value) => + set actions(FluxActions? value) => props[_$key__actions___$ConnectFluxCounterProps] = value; /* GENERATED CONSTANTS */ static const PropDescriptor _$prop__currentCount___$ConnectFluxCounterProps = @@ -112,8 +113,7 @@ abstract class _$ConnectFluxCounterPropsAccessorsMixin _$prop__mutateStoreDirectly___$ConnectFluxCounterProps = PropDescriptor(_$key__mutateStoreDirectly___$ConnectFluxCounterProps); static const PropDescriptor _$prop__actions___$ConnectFluxCounterProps = - PropDescriptor(_$key__actions___$ConnectFluxCounterProps, - isRequired: true, isNullable: true); + PropDescriptor(_$key__actions___$ConnectFluxCounterProps); static const String _$key__currentCount___$ConnectFluxCounterProps = 'ConnectFluxCounterProps.currentCount'; static const String _$key__wrapperStyles___$ConnectFluxCounterProps = diff --git a/test/over_react_redux/fixtures/counter.dart b/test/over_react_redux/fixtures/counter.dart index 70fbfe694..860618d1b 100644 --- a/test/over_react_redux/fixtures/counter.dart +++ b/test/over_react_redux/fixtures/counter.dart @@ -50,7 +50,7 @@ class CounterComponent extends UiComponent2 { if (props.increment != null) { props.increment!(); } else if (props.dispatch != null) { - props.dispatch!(IncrementAction()); + props.dispatch(IncrementAction()); } } )('+'), @@ -59,25 +59,21 @@ class CounterComponent extends UiComponent2 { ..onClick = (_) { if (props.decrement != null) { props.decrement!(); - } else if (props.dispatch != null) { - props.dispatch!(DecrementAction()); + } else { + props.dispatch(DecrementAction()); } } )('-'), (Dom.button() ..addTestId('button-model-increment') ..onClick = (_) { - if (props.dispatch != null) { - props.dispatch!(IncrementModelCountAction()); - } + props.dispatch(IncrementModelCountAction()); } )('+'), (Dom.button() ..addTestId('button-model-decrement') ..onClick = (_) { - if (props.dispatch != null) { - props.dispatch!(DecrementModelCountAction()); - } + props.dispatch(DecrementModelCountAction()); } )('-'), props.children, diff --git a/test/over_react_redux/fixtures/counter_fn.over_react.g.dart b/test/over_react_redux/fixtures/counter_fn.over_react.g.dart index b162d752e..ea58ce538 100644 --- a/test/over_react_redux/fixtures/counter_fn.over_react.g.dart +++ b/test/over_react_redux/fixtures/counter_fn.over_react.g.dart @@ -32,6 +32,12 @@ mixin $CounterFnProps on CounterFnProps { static const List $propKeys = [ _$key__countEqualityFn__CounterFnProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -71,6 +77,12 @@ mixin $ModelCounterFnPropsMixin on ModelCounterFnPropsMixin { static const List $propKeys = [ _$key__modelCountEqualityFn__ModelCounterFnPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -110,6 +122,12 @@ mixin $CustomContextCounterFnPropsMixin on CustomContextCounterFnPropsMixin { static const List $propKeys = [ _$key__bigCountEqualityFn__CustomContextCounterFnPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react_redux/hooks/use_dispatch_test.over_react.g.dart b/test/over_react_redux/hooks/use_dispatch_test.over_react.g.dart index 64bf7d62a..5105c7b45 100644 --- a/test/over_react_redux/hooks/use_dispatch_test.over_react.g.dart +++ b/test/over_react_redux/hooks/use_dispatch_test.over_react.g.dart @@ -17,6 +17,12 @@ mixin $UseDispatchCounterFnProps on UseDispatchCounterFnProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -37,6 +43,12 @@ mixin $CustomContextUseDispatchCounterFnProps static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react_redux/hooks/use_store_test.over_react.g.dart b/test/over_react_redux/hooks/use_store_test.over_react.g.dart index 3724633b6..698e88447 100644 --- a/test/over_react_redux/hooks/use_store_test.over_react.g.dart +++ b/test/over_react_redux/hooks/use_store_test.over_react.g.dart @@ -17,6 +17,12 @@ mixin $UseStoreCounterFnProps on UseStoreCounterFnProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -37,6 +43,12 @@ mixin $CustomContextUseStoreCounterFnProps static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test/over_react_redux/store_bindings_tests.over_react.g.dart b/test/over_react_redux/store_bindings_tests.over_react.g.dart index 35cdefac8..fdf7966f1 100644 --- a/test/over_react_redux/store_bindings_tests.over_react.g.dart +++ b/test/over_react_redux/store_bindings_tests.over_react.g.dart @@ -45,6 +45,12 @@ mixin $TestSelectorProps on TestSelectorProps { _$key__onRender__TestSelectorProps, _$key__equality__TestSelectorProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -79,6 +85,12 @@ mixin $TestConnectPropsMixin on TestConnectPropsMixin { static const List $propKeys = [ _$key__interestingValue__TestConnectPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test_fixtures/gold_output_files/mixin_based/basic.over_react.g.dart.goldFile b/test_fixtures/gold_output_files/mixin_based/basic.over_react.g.dart.goldFile index e27f18e3d..4389680b1 100644 --- a/test_fixtures/gold_output_files/mixin_based/basic.over_react.g.dart.goldFile +++ b/test_fixtures/gold_output_files/mixin_based/basic.over_react.g.dart.goldFile @@ -225,6 +225,12 @@ mixin $BasicProps on BasicProps { _$key__basic4__BasicProps, _$key__basic5__BasicProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test_fixtures/gold_output_files/mixin_based/basic_library.over_react.g.dart.goldFile b/test_fixtures/gold_output_files/mixin_based/basic_library.over_react.g.dart.goldFile index aaa148e5e..c054cf510 100644 --- a/test_fixtures/gold_output_files/mixin_based/basic_library.over_react.g.dart.goldFile +++ b/test_fixtures/gold_output_files/mixin_based/basic_library.over_react.g.dart.goldFile @@ -338,6 +338,12 @@ mixin $BasicPartOfLibPropsMixin on BasicPartOfLibPropsMixin { _$key__basic4__BasicPartOfLibPropsMixin, _$key__basic5__BasicPartOfLibPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -560,6 +566,12 @@ mixin $SuperPartOfLibPropsMixin on SuperPartOfLibPropsMixin { static const List $propKeys = [ _$key__superProp__SuperPartOfLibPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -593,6 +605,12 @@ mixin $SubPartOfLibPropsMixin on SubPartOfLibPropsMixin { static const List $propKeys = [ _$key__subProp__SubPartOfLibPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test_fixtures/gold_output_files/mixin_based/basic_two_nine.over_react.g.dart.goldFile b/test_fixtures/gold_output_files/mixin_based/basic_two_nine.over_react.g.dart.goldFile index d40664a09..baf2699e0 100644 --- a/test_fixtures/gold_output_files/mixin_based/basic_two_nine.over_react.g.dart.goldFile +++ b/test_fixtures/gold_output_files/mixin_based/basic_two_nine.over_react.g.dart.goldFile @@ -225,6 +225,12 @@ mixin $BasicProps on BasicProps { _$key__basic4__BasicProps, _$key__basic5__BasicProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test_fixtures/gold_output_files/mixin_based/props_mixin.over_react.g.dart.goldFile b/test_fixtures/gold_output_files/mixin_based/props_mixin.over_react.g.dart.goldFile index 6e579b5dd..79bc04ed2 100644 --- a/test_fixtures/gold_output_files/mixin_based/props_mixin.over_react.g.dart.goldFile +++ b/test_fixtures/gold_output_files/mixin_based/props_mixin.over_react.g.dart.goldFile @@ -30,6 +30,12 @@ mixin $ExamplePropsMixin on ExamplePropsMixin { _$prop__propMixin1__ExamplePropsMixin ]; static const List $propKeys = [_$key__propMixin1__ExamplePropsMixin]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -66,6 +72,12 @@ mixin $RequiresOtherMixinPropsMixin static const List $propKeys = [ _$key__otherPropMixin__RequiresOtherMixinPropsMixin ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test_fixtures/gold_output_files/mixin_based/two_nine_with_multiple_factories.over_react.g.dart.goldfile b/test_fixtures/gold_output_files/mixin_based/two_nine_with_multiple_factories.over_react.g.dart.goldfile index 7176ed98b..3510f1f04 100644 --- a/test_fixtures/gold_output_files/mixin_based/two_nine_with_multiple_factories.over_react.g.dart.goldfile +++ b/test_fixtures/gold_output_files/mixin_based/two_nine_with_multiple_factories.over_react.g.dart.goldfile @@ -174,6 +174,12 @@ mixin $CounterPropsMixin on CounterPropsMixin { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/test_fixtures/gold_output_files/mixin_based/type_parameters.over_react.g.dart.goldFile b/test_fixtures/gold_output_files/mixin_based/type_parameters.over_react.g.dart.goldFile index 3d570d1bb..1e60851a6 100644 --- a/test_fixtures/gold_output_files/mixin_based/type_parameters.over_react.g.dart.goldFile +++ b/test_fixtures/gold_output_files/mixin_based/type_parameters.over_react.g.dart.goldFile @@ -18,6 +18,12 @@ mixin $NoneProps on NoneProps { static const List $props = []; static const List $propKeys = []; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -44,6 +50,12 @@ mixin $SingleProps on SingleProps { static const List $props = [_$prop__single__SingleProps]; static const List $propKeys = [_$key__single__SingleProps]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -82,6 +94,12 @@ mixin $SingleThatWontBeSpecifiedProps on SingleThatWontBeSpecifiedProps { static const List $propKeys = [ _$key__singleThatWontBeSpecified__SingleThatWontBeSpecifiedProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -115,6 +133,12 @@ mixin $SingleWithBoundProps on SingleWithBoundProps { static const List $propKeys = [ _$key__singleWithBound__SingleWithBoundProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' @@ -154,6 +178,12 @@ mixin $DoubleProps on DoubleProps { _$key__doubleA__DoubleProps, _$key__doubleB__DoubleProps ]; + + @override + @mustCallSuper + void validateRequiredProps() { + super.validateRequiredProps(); + } } @Deprecated('This API is for use only within generated code.' diff --git a/web/over_react_redux/examples/dev_tools/components/counter.dart b/web/over_react_redux/examples/dev_tools/components/counter.dart index 1b6aa6cd1..e3954656f 100644 --- a/web/over_react_redux/examples/dev_tools/components/counter.dart +++ b/web/over_react_redux/examples/dev_tools/components/counter.dart @@ -71,15 +71,15 @@ class CounterComponent extends UiComponent2 { (Dom.button()..onClick = (_) { if (props.increment != null) { props.increment!(); - } else if (props.dispatch != null) { - props.dispatch!(SmallIncrementAction()); + } else { + props.dispatch(SmallIncrementAction()); } })('+'), (Dom.button()..onClick = (_) { if (props.decrement != null) { props.decrement!(); - } else if (props.dispatch != null) { - props.dispatch!(SmallDecrementAction()); + } else { + props.dispatch(SmallDecrementAction()); } })('-'), props.children diff --git a/web/over_react_redux/examples/multiple_stores/components/counter.dart b/web/over_react_redux/examples/multiple_stores/components/counter.dart index 5f2ba6269..adf07e08f 100644 --- a/web/over_react_redux/examples/multiple_stores/components/counter.dart +++ b/web/over_react_redux/examples/multiple_stores/components/counter.dart @@ -49,15 +49,15 @@ class CounterComponent extends UiComponent2 { (Dom.button()..onClick = (_) { if (props.increment != null) { props.increment!(); - } else if (props.dispatch != null) { - props.dispatch!(IncrementAction()); + } else { + props.dispatch(IncrementAction()); } })('+'), (Dom.button()..onClick = (_) { if (props.decrement != null) { props.decrement!(); - } else if (props.dispatch != null) { - props.dispatch!(DecrementAction()); + } else { + props.dispatch(DecrementAction()); } })('-'), props.children diff --git a/web/over_react_redux/examples/simple/components/counter.dart b/web/over_react_redux/examples/simple/components/counter.dart index 58da8d337..fffae0f80 100644 --- a/web/over_react_redux/examples/simple/components/counter.dart +++ b/web/over_react_redux/examples/simple/components/counter.dart @@ -78,15 +78,15 @@ class CounterComponent extends UiComponent2 { // this will be set via mapDispatchToProps, otherwise it will be null. if (props.increment != null) { props.increment!(); - } else if (props.dispatch != null) { - props.dispatch!(SmallIncrementAction()); + } else { + props.dispatch(SmallIncrementAction()); } })('+'), (Dom.button()..onClick = (_) { if (props.decrement != null) { props.decrement!(); - } else if (props.dispatch != null) { - props.dispatch!(SmallDecrementAction()); + } else { + props.dispatch(SmallDecrementAction()); } })('-'), props.children