From 6d8602537c5954d21e548aadd2c55c996e7ce493 Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 6 Jul 2026 18:00:18 +0100 Subject: [PATCH 1/3] annotated string --- android/build.gradle.kts | 2 +- .../some/android/AndroidResolverProvider.kt | 7 +-- .../compose/AnnotatedStringResolver.kt | 31 ++++++++++++ .../android/AndroidResolverProviderTest.kt | 43 ++++++++++++++++ .../some/android/AndroidServiceLoaderTest.kt | 7 +++ .../compose/AnnotatedStringResolverTest.kt | 49 +++++++++++++++++++ docs/android/index.md | 1 + 7 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 android/src/main/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolver.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolverTest.kt diff --git a/android/build.gradle.kts b/android/build.gradle.kts index ddc09a27..3d1e317f 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -33,7 +33,7 @@ detekt { autoCorrect = true } dependencies { api(projects.core) - testImplementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui) testImplementation(libs.junit) testImplementation(libs.kotlin.test) testImplementation(libs.robolectric) diff --git a/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt b/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt index 1d2a49ba..1a781cf9 100644 --- a/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt +++ b/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt @@ -11,6 +11,7 @@ import dev.appoutlet.some.android.resolver.SizeFResolver import dev.appoutlet.some.android.resolver.SizeResolver import dev.appoutlet.some.android.resolver.UriResolver import dev.appoutlet.some.android.resolver.UserHandleResolver +import dev.appoutlet.some.android.resolver.compose.AnnotatedStringResolver import dev.appoutlet.some.core.Resolver import dev.appoutlet.some.core.ResolverProvider import dev.appoutlet.some.core.StrategyProvider @@ -19,8 +20,7 @@ import kotlin.random.Random /** * Contributes Android-specific resolvers discovered through `ServiceLoader`. * - * The returned resolvers extend the shared `some-core` chain with support for common Android framework - * value types such as `Uri`, geometry classes, `Bundle`, `Pair`, `UserHandle`, and `Color`. + * The returned resolvers extend the shared `some-core` chain with Android-specific type support. */ class AndroidResolverProvider : ResolverProvider { /** @@ -44,7 +44,8 @@ class AndroidResolverProvider : ResolverProvider { SizeFResolver(random), PairResolver(random), UserHandleResolver(random), - ColorResolver(random) + ColorResolver(random), + AnnotatedStringResolver(random) ) } } diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolver.kt new file mode 100644 index 00000000..deaa2330 --- /dev/null +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolver.kt @@ -0,0 +1,31 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.text.AnnotatedString +import dev.appoutlet.some.core.Resolver +import dev.appoutlet.some.core.ResolverChain +import kotlin.random.Random +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +private const val MIN_TEXT_LENGTH = 1 +private const val MAX_TEXT_LENGTH = 20 + +/** + * Resolves [AnnotatedString] types with randomized non-blank text content. + * + * @param random Random source used when generating text. + */ +class AnnotatedStringResolver( + private val random: Random +) : Resolver { + override fun canResolve(type: KType): Boolean = type == typeOf() + + override fun resolve(type: KType, chain: ResolverChain): Any { + return AnnotatedString(generateText()) + } + + private fun generateText(): String { + val length = random.nextInt(MIN_TEXT_LENGTH, MAX_TEXT_LENGTH + 1) + return List(length) { ('a'..'z').random(random) }.joinToString("") + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt b/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt new file mode 100644 index 00000000..c974206b --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt @@ -0,0 +1,43 @@ +package dev.appoutlet.some.android + +import dev.appoutlet.some.android.resolver.BundleResolver +import dev.appoutlet.some.android.resolver.ColorResolver +import dev.appoutlet.some.android.resolver.PairResolver +import dev.appoutlet.some.android.resolver.PointFResolver +import dev.appoutlet.some.android.resolver.PointResolver +import dev.appoutlet.some.android.resolver.RectFResolver +import dev.appoutlet.some.android.resolver.RectResolver +import dev.appoutlet.some.android.resolver.SizeFResolver +import dev.appoutlet.some.android.resolver.SizeResolver +import dev.appoutlet.some.android.resolver.UriResolver +import dev.appoutlet.some.android.resolver.UserHandleResolver +import dev.appoutlet.some.android.resolver.compose.AnnotatedStringResolver +import dev.appoutlet.some.config.DefaultStrategyProvider +import org.junit.Assert.assertEquals +import org.junit.Test +import kotlin.random.Random + +class AndroidResolverProviderTest { + @Test + fun `createResolvers returns the full Android resolver list`() { + val resolvers = AndroidResolverProvider().createResolvers(DefaultStrategyProvider(), Random.Default) + + assertEquals( + listOf( + UriResolver::class, + BundleResolver::class, + RectResolver::class, + RectFResolver::class, + PointResolver::class, + PointFResolver::class, + SizeResolver::class, + SizeFResolver::class, + PairResolver::class, + UserHandleResolver::class, + ColorResolver::class, + AnnotatedStringResolver::class + ), + resolvers.map { it::class } + ) + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/AndroidServiceLoaderTest.kt b/android/src/test/java/dev/appoutlet/some/android/AndroidServiceLoaderTest.kt index 72a2b5c2..8299ea4d 100644 --- a/android/src/test/java/dev/appoutlet/some/android/AndroidServiceLoaderTest.kt +++ b/android/src/test/java/dev/appoutlet/some/android/AndroidServiceLoaderTest.kt @@ -1,6 +1,7 @@ package dev.appoutlet.some.android import android.graphics.Point +import androidx.compose.ui.text.AnnotatedString import dev.appoutlet.some.some import org.junit.Assert.assertTrue import org.junit.Test @@ -17,4 +18,10 @@ class AndroidServiceLoaderTest { val result: Any = some() assertTrue(result is Point) } + + @Test + fun `top-level some resolves AnnotatedString through AndroidResolverProvider`() { + val result: AnnotatedString = some() + assertTrue("Generated AnnotatedString text should not be blank", result.text.isNotBlank()) + } } diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolverTest.kt new file mode 100644 index 00000000..d8f2fb04 --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolverTest.kt @@ -0,0 +1,49 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.text.AnnotatedString +import dev.appoutlet.some.config.NullableStrategy +import dev.appoutlet.some.core.ResolverChain +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import kotlin.random.Random +import kotlin.reflect.typeOf + +@RunWith(RobolectricTestRunner::class) +class AnnotatedStringResolverTest { + @Test + fun `AnnotatedStringResolver generates AnnotatedString values`() { + val resolver = AnnotatedStringResolver(Random.Default) + val result = resolver.resolve(typeOf(), testChain) + assertTrue(result is AnnotatedString) + } + + @Test + fun `AnnotatedStringResolver generates non-blank text`() { + val resolver = AnnotatedStringResolver(Random.Default) + + repeat(20) { + val result = resolver.resolve(typeOf(), testChain) as AnnotatedString + assertTrue("Generated AnnotatedString text should not be blank", result.text.isNotBlank()) + } + } + + @Test + fun `AnnotatedStringResolver canResolve detects AnnotatedString type`() { + val resolver = AnnotatedStringResolver(Random.Default) + assertTrue(resolver.canResolve(typeOf())) + } + + @Test + fun `AnnotatedStringResolver rejects non-AnnotatedString types`() { + val resolver = AnnotatedStringResolver(Random.Default) + assertFalse(resolver.canResolve(typeOf())) + assertFalse(resolver.canResolve(typeOf())) + } + + companion object { + private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) + } +} diff --git a/docs/android/index.md b/docs/android/index.md index 554d6a4a..f8102a73 100644 --- a/docs/android/index.md +++ b/docs/android/index.md @@ -51,6 +51,7 @@ Today, the Android module adds these Android-specific types: | `android.os.Bundle` | `some()` | One random value each for `String`, `Int`, `Long`, `Float`, `Double`, and `Boolean` | | `android.os.UserHandle` | `some()` | API 24+; generated from a random non-system UID | | `android.graphics.Color` | `some()` | API 26+; generated from random RGB channel values | +| `androidx.compose.ui.text.AnnotatedString` | `some()` | Non-blank plain text without span styles | ## Platform constraints From 39a350f078050157dad63446e815abe7f61c338b Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 6 Jul 2026 18:07:16 +0100 Subject: [PATCH 2/3] enhance generate text --- .../some/android/resolver/compose/AnnotatedStringResolver.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolver.kt index deaa2330..370cd736 100644 --- a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolver.kt +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolver.kt @@ -26,6 +26,7 @@ class AnnotatedStringResolver( private fun generateText(): String { val length = random.nextInt(MIN_TEXT_LENGTH, MAX_TEXT_LENGTH + 1) - return List(length) { ('a'..'z').random(random) }.joinToString("") + val chars = CharArray(length) { ('a'..'z').random(random) } + return String(chars) } } From e78acd31ce1a5969e3bf9f4b2ba270058106326a Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 6 Jul 2026 18:07:58 +0100 Subject: [PATCH 3/3] remove roboletric dependency --- .../android/resolver/compose/AnnotatedStringResolverTest.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolverTest.kt index d8f2fb04..221c8650 100644 --- a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolverTest.kt +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/AnnotatedStringResolverTest.kt @@ -6,12 +6,9 @@ import dev.appoutlet.some.core.ResolverChain import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import kotlin.random.Random import kotlin.reflect.typeOf -@RunWith(RobolectricTestRunner::class) class AnnotatedStringResolverTest { @Test fun `AnnotatedStringResolver generates AnnotatedString values`() {