Skip to content

Commit

Permalink
feat(validator): add PhoneValidator helper
Browse files Browse the repository at this point in the history
  • Loading branch information
emri99 committed Sep 3, 2021
1 parent 93cc763 commit 2a3ebbd
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 17 deletions.
17 changes: 9 additions & 8 deletions lib/phone_form_field.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
library phone_number_input;

export 'src/widgets/phone_form_field.dart';
export 'src/widgets/base_phone_form_field.dart';

export 'src/models/selector_config.dart';
export 'src/widgets/country_picker/country_selector.dart';
export 'src/widgets/flag_dial_code_chip.dart';
export 'src/localization/phone_field_localization.dart';
export 'package:phone_numbers_parser/phone_numbers_parser.dart'
show PhoneNumber, PhoneNumberType, PhoneParser, LightPhoneParser;

export 'src/localization/phone_field_localization.dart';
export 'src/models/all_countries.dart';
export 'src/models/country.dart';
export 'src/models/selector_config.dart';
export 'src/validator/phone_validator.dart';
export 'src/widgets/base_phone_form_field.dart';
export 'src/widgets/country_picker/country_selector.dart';
export 'src/widgets/country_picker/country_selector_navigator.dart';
export 'src/models/all_countries.dart';
export 'src/widgets/flag_dial_code_chip.dart';
export 'src/widgets/phone_form_field.dart';
92 changes: 92 additions & 0 deletions lib/src/validator/phone_validator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'package:dart_countries/dart_countries.dart';
import 'package:flutter/widgets.dart';
import 'package:phone_form_field/phone_form_field.dart';

typedef PhoneNumberInputValidator = String? Function(PhoneNumber? phoneNumber);

class PhoneValidator {
static PhoneNumberInputValidator compose(
List<PhoneNumberInputValidator> validators) {
return (valueCandidate) {
for (var validator in validators) {
final validatorResult = validator.call(valueCandidate);
if (validatorResult != null) {
return validatorResult;
}
}
return null;
};
}

static PhoneNumberInputValidator required<T>(
BuildContext context, {
String? errorText,
}) {
final defaultMessage = "Phone number required";
return (PhoneNumber? valueCandidate) {
if (valueCandidate == null || (valueCandidate.nsn.trim().isEmpty)) {
return errorText ??
PhoneFieldLocalization.of(context)?.translate(defaultMessage) ??
defaultMessage;
}
return null;
};
}

static PhoneNumberInputValidator invalid(
BuildContext context, {
String? errorText,
bool useLightParser = false,
}) {
final defaultMessage = "Phone number invalid";
final parser = useLightParser ? LightPhoneParser() : PhoneParser();
return (PhoneNumber? valueCandidate) {
if (valueCandidate == null || !parser.validate(valueCandidate)) {
return errorText ??
PhoneFieldLocalization.of(context)?.translate(defaultMessage) ??
defaultMessage;
}
return null;
};
}

static PhoneNumberInputValidator type(
BuildContext context,
PhoneNumberType expectedType, {
String? errorText,
bool useLightParser = false,
}) {
final defaultMessage = expectedType == PhoneNumberType.mobile
? "Invalid mobile phone number"
: "Invalid landline phone number";
final parser = useLightParser ? LightPhoneParser() : PhoneParser();
return (PhoneNumber? valueCandidate) {
if (valueCandidate == null ||
!parser.validate(valueCandidate, expectedType)) {
return errorText ??
PhoneFieldLocalization.of(context)?.translate(defaultMessage) ??
defaultMessage;
}
return null;
};
}

static PhoneNumberInputValidator country(
BuildContext context,
List<String> expectedCountries, {
String? errorText,
}) {
assert(expectedCountries.every((isoCode) => isoCodes.contains(isoCode)),
'Each expectedCountries value be valid country isoCode');
final defaultMessage = "Invalid country";
return (PhoneNumber? valueCandidate) {
if (valueCandidate == null ||
!expectedCountries.contains(valueCandidate.isoCode)) {
return errorText ??
PhoneFieldLocalization.of(context)?.translate(defaultMessage) ??
defaultMessage;
}
return null;
};
}
}
10 changes: 1 addition & 9 deletions lib/src/widgets/base_phone_form_field.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:phone_form_field/src/localization/phone_field_localization.dart';
import 'package:phone_form_field/src/models/phone_number_input.dart';

import '../models/country.dart';
Expand Down Expand Up @@ -206,7 +205,7 @@ class _BasePhoneFormFieldState extends FormFieldState<PhoneNumberInput> {

InputDecoration _getEffectiveDecoration() {
return widget.decoration.copyWith(
errorText: getErrorText(),
errorText: errorText,
prefix: _getDialCodeChip(),
);
}
Expand All @@ -222,11 +221,4 @@ class _BasePhoneFormFieldState extends FormFieldState<PhoneNumberInput> {
),
);
}

// // which error text to show
String? getErrorText() {
if (errorText != null) {
return PhoneFieldLocalization.of(context)?.translate(errorText!);
}
}
}
140 changes: 140 additions & 0 deletions test/phone_validator_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:phone_form_field/phone_form_field.dart';
import 'package:phone_form_field/src/validator/phone_validator.dart';

void main() async {
Future<BuildContext> getBuildContext(WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: Material(child: Container())));
return tester.element(find.byType(Container));
}

group('PhoneValidator.compose', () {
testWidgets('compose should test each validator until first failure',
(WidgetTester tester) async {
bool firstValidationDone = false;
bool lastValidationDone = false;
final validator = PhoneValidator.compose([
(PhoneNumber? p) {
firstValidationDone = true;
},
(PhoneNumber? p) {
return "validation failed";
},
(PhoneNumber? p) {
lastValidationDone = true;
},
]);
expect(validator(PhoneNumber(isoCode: '', nsn: '')),
equals("validation failed"));
expect(firstValidationDone, isTrue);
expect(lastValidationDone, isFalse);
});
});

group('PhoneValidator.required', () {
testWidgets('should be required value', (WidgetTester tester) async {
final context = await getBuildContext(tester);

final validator = PhoneValidator.required(context);
expect(
validator(PhoneNumber(isoCode: 'US', nsn: '')),
equals("Phone number required"),
);

final validatorWithText = PhoneValidator.required(
context,
errorText: 'custom message',
);
expect(
validatorWithText(PhoneNumber(isoCode: 'US', nsn: '')),
equals("custom message"),
);
});
});

group('PhoneValidator.invalid', () {
testWidgets('should be invalid', (WidgetTester tester) async {
final context = await getBuildContext(tester);

final validator = PhoneValidator.invalid(context);
expect(
validator(PhoneNumber(isoCode: 'FR', nsn: '123')),
equals("Phone number invalid"),
);

final validatorWithText = PhoneValidator.invalid(
context,
errorText: 'custom message',
);
expect(
validatorWithText(PhoneNumber(isoCode: 'FR', nsn: '123')),
equals("custom message"),
);
});
});

group('PhoneValidator.type', () {
testWidgets('should be invalid mobile type', (WidgetTester tester) async {
final context = await getBuildContext(tester);

final validator = PhoneValidator.type(context, PhoneNumberType.mobile);
expect(
validator(PhoneNumber(isoCode: 'FR', nsn: '412345678')),
equals("Invalid mobile phone number"),
);

final validatorWithText = PhoneValidator.type(
context,
PhoneNumberType.mobile,
errorText: 'custom type message',
);
expect(
validatorWithText(PhoneNumber(isoCode: 'FR', nsn: '412345678')),
equals("custom type message"),
);
});

testWidgets('should be invalid landline type', (WidgetTester tester) async {
final context = await getBuildContext(tester);

final validator = PhoneValidator.type(context, PhoneNumberType.fixedLine);
expect(
validator(PhoneNumber(isoCode: 'FR', nsn: '612345678')),
equals("Invalid landline phone number"),
);

final validatorWithText = PhoneValidator.type(
context,
PhoneNumberType.fixedLine,
errorText: 'custom fixed type message',
);
expect(
validatorWithText(PhoneNumber(isoCode: 'FR', nsn: '612345678')),
equals("custom fixed type message"),
);
});
});

group('PhoneValidator.country', () {
testWidgets('should be invalid country', (WidgetTester tester) async {
final context = await getBuildContext(tester);

final validator = PhoneValidator.country(context, ['FR', 'BE']);
expect(
validator(PhoneNumber(isoCode: 'US', nsn: '')),
equals("Invalid country"),
);
});

testWidgets('country validator should refuse invalid isoCode',
(WidgetTester tester) async {
final context = await getBuildContext(tester);

expect(
() => PhoneValidator.country(context, ['INVALID_ISO_CODE']),
throwsAssertionError,
);
});
});
}

0 comments on commit 2a3ebbd

Please sign in to comment.