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())