diff --git a/library/src/androidInstrumentedTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.android.kt b/library/src/androidInstrumentedTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.android.kt new file mode 100644 index 0000000..a215241 --- /dev/null +++ b/library/src/androidInstrumentedTest/kotlin/com/chrisjenx/yakcov/IOSIgnore.android.kt @@ -0,0 +1,4 @@ +package com.chrisjenx.yakcov + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IOSIgnore() \ No newline at end of file diff --git a/library/src/commonMain/composeResources/values/strings.xml b/library/src/commonMain/composeResources/values/strings.xml index 3eee2aa..39f9b00 100644 --- a/library/src/commonMain/composeResources/values/strings.xml +++ b/library/src/commonMain/composeResources/values/strings.xml @@ -11,6 +11,7 @@ Must be at least %1$s Must be at most %1$s Must be at least %1$s characters + Must be at least %1$s characters excluding whitespace Must be at most %1$s characters Invalid email address Invalid phone number diff --git a/library/src/commonMain/kotlin/com/chrisjenx/yakcov/strings/StringValueValidatorRules.kt b/library/src/commonMain/kotlin/com/chrisjenx/yakcov/strings/StringValueValidatorRules.kt index bc80f32..e9ea5aa 100644 --- a/library/src/commonMain/kotlin/com/chrisjenx/yakcov/strings/StringValueValidatorRules.kt +++ b/library/src/commonMain/kotlin/com/chrisjenx/yakcov/strings/StringValueValidatorRules.kt @@ -20,6 +20,7 @@ import yakcov.library.generated.resources.ruleInvalidDate import yakcov.library.generated.resources.ruleMaxLength import yakcov.library.generated.resources.ruleMaxValue import yakcov.library.generated.resources.ruleMinLength +import yakcov.library.generated.resources.ruleMinLengthNoWhitespace import yakcov.library.generated.resources.ruleMinValue import yakcov.library.generated.resources.ruleMonth import yakcov.library.generated.resources.ruleNumeric @@ -40,6 +41,7 @@ data object Required : ValueValidatorRule { @Stable data object Numeric : ValueValidatorRule { override fun validate(value: String): ValidationResult { + if (value.isBlank()) return ResourceValidationResult.success() return if (value.toLongOrNull() == null) { ResourceValidationResult.error(Res.string.ruleNumeric) } else { @@ -52,6 +54,7 @@ data object Numeric : ValueValidatorRule { @Stable data object Decimal : ValueValidatorRule { override fun validate(value: String): ValidationResult { + if (value.isBlank()) return ResourceValidationResult.success() return if (value.toFloatOrNull() == null) { ResourceValidationResult.error(Res.string.ruleDecimal) } else { @@ -63,6 +66,7 @@ data object Decimal : ValueValidatorRule { @Stable data class MinValue(val minValue: Number) : ValueValidatorRule { override fun validate(value: String): ValidationResult { + if (value.isBlank()) return ResourceValidationResult.success() val number = value.toDoubleOrNull() return if (number == null || number < minValue.toDouble()) { ResourceValidationResult.error(Res.string.ruleMinValue, minValue) @@ -75,6 +79,7 @@ data class MinValue(val minValue: Number) : ValueValidatorRule { @Stable data class MaxValue(val maxValue: Float) : ValueValidatorRule { override fun validate(value: String): ValidationResult { + if (value.isBlank()) return ResourceValidationResult.success() val number = value.toFloatOrNull() return if (value.isNotBlank() && (number == null || number > maxValue)) { ResourceValidationResult.error(Res.string.ruleMaxValue, maxValue) @@ -84,13 +89,29 @@ data class MaxValue(val maxValue: Float) : ValueValidatorRule { } } +/** + * @param trim if true will trim the start and end before checking the length + * @param includeWhiteSpace if false will not count white space chars in this string. + * As defined by [Char.isWhitespace] + */ @Stable -data class MinLength(val minLength: Int) : ValueValidatorRule { +data class MinLength( + val minLength: Int, + val trim: Boolean = true, + val includeWhiteSpace: Boolean = true, +) : ValueValidatorRule { override fun validate(value: String): ValidationResult { - return if (value.length < minLength) { - ResourceValidationResult.error(Res.string.ruleMinLength, minLength) - } else { - ResourceValidationResult.success() + val trimmed = value.let { if (trim) it.trim() else it } + return when { + includeWhiteSpace && trimmed.length < minLength -> { + ResourceValidationResult.error(Res.string.ruleMinLength, minLength) + } + + !includeWhiteSpace && trimmed.count { !it.isWhitespace() } < minLength -> { + ResourceValidationResult.error(Res.string.ruleMinLengthNoWhitespace, minLength) + } + + else -> ResourceValidationResult.success() } } } @@ -111,6 +132,7 @@ data class MaxLength(val maxLength: Int) : ValueValidatorRule { data object Email : ValueValidatorRule { override fun validate(value: String): ValidationResult { // only validate if not empty as Required will check if not empty + if (value.isBlank()) return ResourceValidationResult.success() return if (!value.isEmail()) { ResourceValidationResult.error(Res.string.ruleEmail) } else { @@ -127,6 +149,7 @@ data object Email : ValueValidatorRule { data class Phone(val defaultRegion: String = "US") : ValueValidatorRule { override fun validate(value: String): ValidationResult { // only validate if not empty as Required will check if not empty + if (value.isBlank()) return ResourceValidationResult.success() return if (!value.isPhoneNumber(defaultRegion)) { ResourceValidationResult.error(Res.string.rulePhone) } else { diff --git a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/EmailRuleTest.kt b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/EmailRuleTest.kt new file mode 100644 index 0000000..b65d972 --- /dev/null +++ b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/EmailRuleTest.kt @@ -0,0 +1,27 @@ +package com.chrisjenx.yakcov.strings + +import com.chrisjenx.yakcov.IOSIgnore +import com.chrisjenx.yakcov.ValidationResult.Outcome +import com.chrisjenx.yakcov.initPhoneNumberUtil +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class EmailRuleTest { + + @Test + fun email_empty_success() { + assertEquals(Outcome.SUCCESS, Email.validate("").outcome()) + } + + @Test + fun email_invalid_error() { + assertEquals(Outcome.ERROR, Email.validate("43435").outcome()) + } + + @Test + fun email_valid_success() { + assertEquals(Outcome.SUCCESS, Email.validate("me@me.com").outcome()) + } + +} diff --git a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt index 9ed5a38..f2fc753 100644 --- a/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt +++ b/library/src/commonTest/kotlin/com/chrisjenx/yakcov/strings/PhoneRuleTest.kt @@ -15,7 +15,12 @@ class PhoneRuleTest { } @Test - fun phoneNumber_invalid() { + fun phoneNumber_empty_success() { + assertEquals(Outcome.SUCCESS, Phone().validate("").outcome()) + } + + @Test + fun phoneNumber_invalid_error() { assertEquals(Outcome.ERROR, Phone().validate("43435").outcome()) } @@ -25,7 +30,7 @@ class PhoneRuleTest { } @Test - @IOSIgnore + @IOSIgnore // FIXME: This is failing on iOS, get cocoapods working so we can use libphonenumber on iOS. fun phoneNumber_wrongRegion() { // This is a UK number should error for US assertEquals(Outcome.ERROR, Phone("US").validate("07745973912").outcome())