testRequiredProps function

  1. @isTestGroup
void testRequiredProps(
  1. BuilderOnlyUiFactory<UiProps> factory,
  2. dynamic childrenFactory()
)

Common test for verifying that props annotated as a requiredProp are validated correctly.

Typically not consumed standalone. Use commonComponentTests instead.

Note: All required props must be provided by factory.

Implementation

@isTestGroup
void testRequiredProps(BuilderOnlyUiFactory factory, dynamic childrenFactory()) {
  late bool isComponent2;

  var keyToErrorMessage = {};
  var nullableProps = <String>[];
  var requiredProps = <String>[];

  setUp(() {
    // This can't go in a setUpAll since it would be called before consumer setUps.
    //
    // ignore: invalid_use_of_protected_member
    final version = ReactDartComponentVersion.fromType(
      (factory()(childrenFactory())).type,
    );
    // ignore: invalid_use_of_protected_member
    isComponent2 = version == ReactDartComponentVersion.component2;

    var jacket = mount(factory()(childrenFactory()), autoTearDown: false);
    var consumedProps = (jacket.getDartInstance() as component_base.UiComponent).consumedProps!;
    jacket.unmount();

    for (var consumedProp in consumedProps) {
      for (var prop in consumedProp.props) {
        if (!prop.isLate) {
          if (prop.isNullable) {
            nullableProps.add(prop.key);
          } else if (prop.isRequired) {
            requiredProps.add(prop.key);
          }
        }

        keyToErrorMessage[prop.key] = prop.errorMessage;
      }
    }
  });

  testFunction('throws (component1) or logs the correct errors (component2) when the required prop is not set or is null', () {
    void component1RequiredPropsTest(){
      for (var propKey in requiredProps) {
        final reactComponentFactory = factory()
            .componentFactory as ReactDartComponentFactoryProxy; // ignore: avoid_as

        // Props that are defined in the default props map will never not be set.
        if (!reactComponentFactory.defaultProps.containsKey(propKey)) {
          var badRenderer = () =>
              render((factory()
                ..remove(propKey)
              )(childrenFactory()));

          expect(badRenderer,
              throwsPropError_Required(propKey, keyToErrorMessage[propKey]),
              reason: '$propKey is not set');
        }

        var propsToAdd = {propKey: null};
        var badRenderer = () =>
            render((factory()
              ..addAll(propsToAdd)
            )(childrenFactory()));

        expect(badRenderer,
            throwsPropError_Required(propKey, keyToErrorMessage[propKey]),
            reason: '$propKey is set to null');
      }
    }

    void component2RequiredPropsTest() {
      PropTypes.resetWarningCache();

      var consoleErrors = <String?>[];
      final originalConsoleError = context['console']['error'] as JsFunction;
      addTearDown(() => context['console']['error'] = originalConsoleError);
      context['console']['error'] = JsFunction.withThis((self, [message, arg1, arg2, arg3,  arg4, arg5]) {
        consoleErrors.add(message);
        originalConsoleError.apply([message, arg1, arg2, arg3,  arg4, arg5],
            thisArg: self);
      });

      final reactComponentFactory = factory().componentFactory as
          ReactDartComponentFactoryProxy2; // ignore: avoid_as

      for (var propKey in requiredProps) {
        if (!reactComponentFactory.defaultProps.containsKey(propKey)) {

          try {
            mount((factory()
              ..remove(propKey)
            )(childrenFactory()));
          } catch (_){}

          expect(consoleErrors, isNotEmpty, reason: 'should have outputted a warning');

          if (keyToErrorMessage[propKey] != '') {
            expect(consoleErrors, [contains(keyToErrorMessage[propKey])],
                reason: '$propKey is not set');
          }

          consoleErrors = [];
          PropTypes.resetWarningCache();
        }

        var propsToAdd = {propKey: null};

        try {
          mount((factory()
            ..addAll(propsToAdd)
          )(childrenFactory()));
        } catch (_) {}

        expect(consoleErrors, isNotEmpty, reason: 'should have outputted a warning');

        if (keyToErrorMessage[propKey] != '') {
          expect(consoleErrors, [contains(keyToErrorMessage[propKey])],
              reason: '$propKey is not set');
        }

        consoleErrors = [];
        PropTypes.resetWarningCache();
      }
    }

    isComponent2 ? component2RequiredPropsTest() : component1RequiredPropsTest();
  });

  testFunction('nullable props', () {
    if (!isComponent2) {
      for (var propKey in nullableProps) {
        final reactComponentFactory = factory().componentFactory as
          ReactDartComponentFactoryProxy; // ignore: avoid_as
        // Props that are defined in the default props map will never not be set.
        if (!reactComponentFactory.defaultProps.containsKey(propKey)) {
          var badRenderer = () => render((factory()..remove(propKey))(childrenFactory()));

          expect(badRenderer, throwsPropError_Required(propKey, keyToErrorMessage[propKey]), reason: 'should throw when the required, nullable prop $propKey is not set');

          var propsToAdd = {propKey: null};
          badRenderer = () => render((factory()
            ..addAll(propsToAdd)
          )(childrenFactory()));

          expect(badRenderer, returnsNormally, reason: 'does not throw when the required, nullable prop $propKey is set to null');
        }
      }
    } else {
      PropTypes.resetWarningCache();

      var consoleErrors = <String?>[];
      final originalConsoleError = context['console']['error'] as JsFunction;
      addTearDown(() => context['console']['error'] = originalConsoleError);
      context['console']['error'] = JsFunction.withThis((self, [message, arg1, arg2, arg3,  arg4, arg5]) {
        consoleErrors.add(message);
        originalConsoleError.apply([message, arg1, arg2, arg3,  arg4, arg5],
            thisArg: self);
      });

      final reactComponentFactory = factory().componentFactory as
          ReactDartComponentFactoryProxy2; // ignore: avoid_as

      for (var propKey in nullableProps) {
        // Props that are defined in the default props map will never not be set.
        if (!reactComponentFactory.defaultProps.containsKey(propKey)) {
          try {
            mount((factory()
              ..remove(propKey)
            )(childrenFactory()));
          } catch(_) {}
          expect(consoleErrors, isNotEmpty, reason: 'should have outputted a warning');

          if (keyToErrorMessage[propKey] != '') {
            expect(consoleErrors, [contains(keyToErrorMessage[propKey])], reason: '$propKey is not set');
          }

          consoleErrors = [];
          PropTypes.resetWarningCache();
        }

        var propsToAdd = {propKey: null};

        try {
          mount((factory()
            ..addAll(propsToAdd)
          )(childrenFactory()));
        } catch (_) {}

        expect(consoleErrors, isEmpty, reason: 'should not have output a warning');

        consoleErrors = [];
        PropTypes.resetWarningCache();
      }
    }
  });
}