From f1dedcc012247f9a9f58b0c9748cc7d685e471a8 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:54:36 +0000 Subject: [PATCH 1/2] Implement Compose unit and geometry resolvers - Added DpResolver, SpResolver, OffsetResolver, SizeResolver, RectResolver, IntOffsetResolver, and IntSizeResolver. - Registered new resolvers in AndroidResolverProvider. - Added JVM tests for all new resolvers. - Created dedicated Compose documentation page. - Updated Android and supported types documentation. --- .../some/android/AndroidResolverProvider.kt | 16 +++++- .../android/resolver/compose/DpResolver.kt | 25 ++++++++++ .../resolver/compose/IntOffsetResolver.kt | 27 ++++++++++ .../resolver/compose/IntSizeResolver.kt | 27 ++++++++++ .../resolver/compose/OffsetResolver.kt | 27 ++++++++++ .../android/resolver/compose/RectResolver.kt | 31 ++++++++++++ .../android/resolver/compose/SizeResolver.kt | 27 ++++++++++ .../android/resolver/compose/SpResolver.kt | 26 ++++++++++ .../android/AndroidResolverProviderTest.kt | 16 +++++- .../some/android/AndroidServiceLoaderTest.kt | 18 +++++++ .../resolver/compose/DpResolverTest.kt | 48 ++++++++++++++++++ .../resolver/compose/IntOffsetResolverTest.kt | 39 +++++++++++++++ .../resolver/compose/IntSizeResolverTest.kt | 49 ++++++++++++++++++ .../resolver/compose/OffsetResolverTest.kt | 39 +++++++++++++++ .../resolver/compose/RectResolverTest.kt | 49 ++++++++++++++++++ .../resolver/compose/SizeResolverTest.kt | 49 ++++++++++++++++++ .../resolver/compose/SpResolverTest.kt | 50 +++++++++++++++++++ docs/android/compose.md | 16 ++++++ docs/android/index.md | 7 ++- docs/supported-types.md | 3 ++ zensical.toml | 3 +- 21 files changed, 588 insertions(+), 4 deletions(-) create mode 100644 android/src/main/java/dev/appoutlet/some/android/resolver/compose/DpResolver.kt create mode 100644 android/src/main/java/dev/appoutlet/some/android/resolver/compose/IntOffsetResolver.kt create mode 100644 android/src/main/java/dev/appoutlet/some/android/resolver/compose/IntSizeResolver.kt create mode 100644 android/src/main/java/dev/appoutlet/some/android/resolver/compose/OffsetResolver.kt create mode 100644 android/src/main/java/dev/appoutlet/some/android/resolver/compose/RectResolver.kt create mode 100644 android/src/main/java/dev/appoutlet/some/android/resolver/compose/SizeResolver.kt create mode 100644 android/src/main/java/dev/appoutlet/some/android/resolver/compose/SpResolver.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/resolver/compose/DpResolverTest.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/resolver/compose/IntOffsetResolverTest.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/resolver/compose/IntSizeResolverTest.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/resolver/compose/OffsetResolverTest.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/resolver/compose/RectResolverTest.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/resolver/compose/SizeResolverTest.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/resolver/compose/SpResolverTest.kt create mode 100644 docs/android/compose.md 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 1a781cf9..fa64e182 100644 --- a/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt +++ b/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt @@ -12,6 +12,13 @@ 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.android.resolver.compose.DpResolver +import dev.appoutlet.some.android.resolver.compose.IntOffsetResolver +import dev.appoutlet.some.android.resolver.compose.IntSizeResolver +import dev.appoutlet.some.android.resolver.compose.OffsetResolver +import dev.appoutlet.some.android.resolver.compose.RectResolver as ComposeRectResolver +import dev.appoutlet.some.android.resolver.compose.SizeResolver as ComposeSizeResolver +import dev.appoutlet.some.android.resolver.compose.SpResolver import dev.appoutlet.some.core.Resolver import dev.appoutlet.some.core.ResolverProvider import dev.appoutlet.some.core.StrategyProvider @@ -45,7 +52,14 @@ class AndroidResolverProvider : ResolverProvider { PairResolver(random), UserHandleResolver(random), ColorResolver(random), - AnnotatedStringResolver(random) + AnnotatedStringResolver(random), + DpResolver(random), + SpResolver(random), + OffsetResolver(random), + ComposeSizeResolver(random), + ComposeRectResolver(random), + IntOffsetResolver(random), + IntSizeResolver(random) ) } } diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/DpResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/DpResolver.kt new file mode 100644 index 00000000..0f046126 --- /dev/null +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/DpResolver.kt @@ -0,0 +1,25 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.unit.Dp +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 MAX_DP = 1000f + +/** + * Resolves [Dp] types with randomized float values. + * + * @param random Random source used when generating values. + */ +class DpResolver( + private val random: Random +) : Resolver { + override fun canResolve(type: KType): Boolean = type == typeOf() + + override fun resolve(type: KType, chain: ResolverChain): Any { + return Dp(random.nextFloat() * MAX_DP) + } +} diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/IntOffsetResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/IntOffsetResolver.kt new file mode 100644 index 00000000..7f537015 --- /dev/null +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/IntOffsetResolver.kt @@ -0,0 +1,27 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.unit.IntOffset +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 COORDINATE_BOUND = 1000 + +/** + * Resolves [IntOffset] types with randomized integer coordinates. + * + * @param random Random source used when generating coordinates. + */ +class IntOffsetResolver( + private val random: Random +) : Resolver { + override fun canResolve(type: KType): Boolean = type == typeOf() + + override fun resolve(type: KType, chain: ResolverChain): Any { + val x = random.nextInt(-COORDINATE_BOUND, COORDINATE_BOUND) + val y = random.nextInt(-COORDINATE_BOUND, COORDINATE_BOUND) + return IntOffset(x, y) + } +} diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/IntSizeResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/IntSizeResolver.kt new file mode 100644 index 00000000..cf921a99 --- /dev/null +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/IntSizeResolver.kt @@ -0,0 +1,27 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.unit.IntSize +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 MAX_DIMENSION = 1000 + +/** + * Resolves [IntSize] types with randomized positive integer dimensions. + * + * @param random Random source used when generating dimensions. + */ +class IntSizeResolver( + private val random: Random +) : Resolver { + override fun canResolve(type: KType): Boolean = type == typeOf() + + override fun resolve(type: KType, chain: ResolverChain): Any { + val width = random.nextInt(1, MAX_DIMENSION) + val height = random.nextInt(1, MAX_DIMENSION) + return IntSize(width, height) + } +} diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/OffsetResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/OffsetResolver.kt new file mode 100644 index 00000000..a9aab334 --- /dev/null +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/OffsetResolver.kt @@ -0,0 +1,27 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.geometry.Offset +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 COORDINATE_BOUND = 1000f + +/** + * Resolves [Offset] types with randomized float coordinates. + * + * @param random Random source used when generating coordinates. + */ +class OffsetResolver( + private val random: Random +) : Resolver { + override fun canResolve(type: KType): Boolean = type == typeOf() + + override fun resolve(type: KType, chain: ResolverChain): Any { + val x = (random.nextFloat() * 2 - 1) * COORDINATE_BOUND + val y = (random.nextFloat() * 2 - 1) * COORDINATE_BOUND + return Offset(x, y) + } +} diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/RectResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/RectResolver.kt new file mode 100644 index 00000000..48d809f9 --- /dev/null +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/RectResolver.kt @@ -0,0 +1,31 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.geometry.Rect +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 COORDINATE_BOUND = 1000f + +/** + * Resolves [Rect] types with randomized float bounds. + * + * Generated rectangles always satisfy `left < right` and `top < bottom`. + * + * @param random Random source used when generating coordinates. + */ +class RectResolver( + private val random: Random +) : Resolver { + override fun canResolve(type: KType): Boolean = type == typeOf() + + override fun resolve(type: KType, chain: ResolverChain): Any { + val left = (random.nextFloat() * 2 - 1) * COORDINATE_BOUND + val top = (random.nextFloat() * 2 - 1) * COORDINATE_BOUND + val right = left + random.nextFloat() * COORDINATE_BOUND + val bottom = top + random.nextFloat() * COORDINATE_BOUND + return Rect(left, top, right, bottom) + } +} diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/SizeResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/SizeResolver.kt new file mode 100644 index 00000000..6d263bf1 --- /dev/null +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/SizeResolver.kt @@ -0,0 +1,27 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.geometry.Size +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 MAX_DIMENSION = 1000f + +/** + * Resolves [Size] types with randomized positive float dimensions. + * + * @param random Random source used when generating dimensions. + */ +class SizeResolver( + private val random: Random +) : Resolver { + override fun canResolve(type: KType): Boolean = type == typeOf() + + override fun resolve(type: KType, chain: ResolverChain): Any { + val width = random.nextFloat() * MAX_DIMENSION + val height = random.nextFloat() * MAX_DIMENSION + return Size(width, height) + } +} diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/compose/SpResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/SpResolver.kt new file mode 100644 index 00000000..e761f647 --- /dev/null +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/compose/SpResolver.kt @@ -0,0 +1,26 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp +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 MAX_SP = 100f + +/** + * Resolves [TextUnit] types with randomized float values in `sp`. + * + * @param random Random source used when generating values. + */ +class SpResolver( + private val random: Random +) : Resolver { + override fun canResolve(type: KType): Boolean = type == typeOf() + + override fun resolve(type: KType, chain: ResolverChain): Any { + return (random.nextFloat() * MAX_SP).sp + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt b/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt index c974206b..9e7b5700 100644 --- a/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt +++ b/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt @@ -12,6 +12,13 @@ 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.android.resolver.compose.DpResolver +import dev.appoutlet.some.android.resolver.compose.IntOffsetResolver +import dev.appoutlet.some.android.resolver.compose.IntSizeResolver +import dev.appoutlet.some.android.resolver.compose.OffsetResolver +import dev.appoutlet.some.android.resolver.compose.RectResolver as ComposeRectResolver +import dev.appoutlet.some.android.resolver.compose.SizeResolver as ComposeSizeResolver +import dev.appoutlet.some.android.resolver.compose.SpResolver import dev.appoutlet.some.config.DefaultStrategyProvider import org.junit.Assert.assertEquals import org.junit.Test @@ -35,7 +42,14 @@ class AndroidResolverProviderTest { PairResolver::class, UserHandleResolver::class, ColorResolver::class, - AnnotatedStringResolver::class + AnnotatedStringResolver::class, + DpResolver::class, + SpResolver::class, + OffsetResolver::class, + ComposeSizeResolver::class, + ComposeRectResolver::class, + IntOffsetResolver::class, + IntSizeResolver::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 8299ea4d..eca39bfa 100644 --- a/android/src/test/java/dev/appoutlet/some/android/AndroidServiceLoaderTest.kt +++ b/android/src/test/java/dev/appoutlet/some/android/AndroidServiceLoaderTest.kt @@ -1,7 +1,14 @@ package dev.appoutlet.some.android import android.graphics.Point +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.TextUnit import dev.appoutlet.some.some import org.junit.Assert.assertTrue import org.junit.Test @@ -24,4 +31,15 @@ class AndroidServiceLoaderTest { val result: AnnotatedString = some() assertTrue("Generated AnnotatedString text should not be blank", result.text.isNotBlank()) } + + @Test + fun `top-level some resolves Compose types through AndroidResolverProvider`() { + assertTrue(some() is Dp) + assertTrue(some().isSp) + assertTrue(some() is Offset) + assertTrue(some() is Size) + assertTrue(some() is Rect) + assertTrue(some() is IntOffset) + assertTrue(some() is IntSize) + } } diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/DpResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/DpResolverTest.kt new file mode 100644 index 00000000..6b277298 --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/DpResolverTest.kt @@ -0,0 +1,48 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.unit.Dp +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 DpResolverTest { + @Test + fun `DpResolver generates Dp values`() { + val resolver = DpResolver(Random.Default) + val result = resolver.resolve(typeOf(), testChain) + assertTrue(result is Dp) + } + + @Test + fun `DpResolver generates values within range`() { + val resolver = DpResolver(Random.Default) + repeat(100) { + val result = resolver.resolve(typeOf(), testChain) as Dp + assertTrue(result.value in 0f..1000f) + } + } + + @Test + fun `DpResolver canResolve detects Dp type`() { + val resolver = DpResolver(Random.Default) + assertTrue(resolver.canResolve(typeOf())) + } + + @Test + fun `DpResolver rejects non-Dp types`() { + val resolver = DpResolver(Random.Default) + assertFalse(resolver.canResolve(typeOf())) + assertFalse(resolver.canResolve(typeOf())) + } + + companion object { + private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/IntOffsetResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/IntOffsetResolverTest.kt new file mode 100644 index 00000000..11ed7798 --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/IntOffsetResolverTest.kt @@ -0,0 +1,39 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.unit.IntOffset +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 IntOffsetResolverTest { + @Test + fun `IntOffsetResolver generates IntOffset values`() { + val resolver = IntOffsetResolver(Random.Default) + val result = resolver.resolve(typeOf(), testChain) + assertTrue(result is IntOffset) + } + + @Test + fun `IntOffsetResolver canResolve detects IntOffset type`() { + val resolver = IntOffsetResolver(Random.Default) + assertTrue(resolver.canResolve(typeOf())) + } + + @Test + fun `IntOffsetResolver rejects non-IntOffset types`() { + val resolver = IntOffsetResolver(Random.Default) + assertFalse(resolver.canResolve(typeOf())) + assertFalse(resolver.canResolve(typeOf())) + } + + companion object { + private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/IntSizeResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/IntSizeResolverTest.kt new file mode 100644 index 00000000..9d92cc97 --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/IntSizeResolverTest.kt @@ -0,0 +1,49 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.unit.IntSize +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 IntSizeResolverTest { + @Test + fun `IntSizeResolver generates IntSize values`() { + val resolver = IntSizeResolver(Random.Default) + val result = resolver.resolve(typeOf(), testChain) + assertTrue(result is IntSize) + } + + @Test + fun `IntSizeResolver generates positive dimensions`() { + val resolver = IntSizeResolver(Random.Default) + repeat(100) { + val result = resolver.resolve(typeOf(), testChain) as IntSize + assertTrue(result.width >= 0) + assertTrue(result.height >= 0) + } + } + + @Test + fun `IntSizeResolver canResolve detects IntSize type`() { + val resolver = IntSizeResolver(Random.Default) + assertTrue(resolver.canResolve(typeOf())) + } + + @Test + fun `IntSizeResolver rejects non-IntSize types`() { + val resolver = IntSizeResolver(Random.Default) + assertFalse(resolver.canResolve(typeOf())) + assertFalse(resolver.canResolve(typeOf())) + } + + companion object { + private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/OffsetResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/OffsetResolverTest.kt new file mode 100644 index 00000000..a72dc8b7 --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/OffsetResolverTest.kt @@ -0,0 +1,39 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.geometry.Offset +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 OffsetResolverTest { + @Test + fun `OffsetResolver generates Offset values`() { + val resolver = OffsetResolver(Random.Default) + val result = resolver.resolve(typeOf(), testChain) + assertTrue(result is Offset) + } + + @Test + fun `OffsetResolver canResolve detects Offset type`() { + val resolver = OffsetResolver(Random.Default) + assertTrue(resolver.canResolve(typeOf())) + } + + @Test + fun `OffsetResolver rejects non-Offset types`() { + val resolver = OffsetResolver(Random.Default) + assertFalse(resolver.canResolve(typeOf())) + assertFalse(resolver.canResolve(typeOf())) + } + + companion object { + private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/RectResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/RectResolverTest.kt new file mode 100644 index 00000000..27e3291f --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/RectResolverTest.kt @@ -0,0 +1,49 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.geometry.Rect +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 RectResolverTest { + @Test + fun `RectResolver generates Rect values`() { + val resolver = RectResolver(Random.Default) + val result = resolver.resolve(typeOf(), testChain) + assertTrue(result is Rect) + } + + @Test + fun `RectResolver generates valid bounds`() { + val resolver = RectResolver(Random.Default) + repeat(100) { + val result = resolver.resolve(typeOf(), testChain) as Rect + assertTrue(result.left < result.right) + assertTrue(result.top < result.bottom) + } + } + + @Test + fun `RectResolver canResolve detects Rect type`() { + val resolver = RectResolver(Random.Default) + assertTrue(resolver.canResolve(typeOf())) + } + + @Test + fun `RectResolver rejects non-Rect types`() { + val resolver = RectResolver(Random.Default) + assertFalse(resolver.canResolve(typeOf())) + assertFalse(resolver.canResolve(typeOf())) + } + + companion object { + private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/SizeResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/SizeResolverTest.kt new file mode 100644 index 00000000..48549c43 --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/SizeResolverTest.kt @@ -0,0 +1,49 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.geometry.Size +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 SizeResolverTest { + @Test + fun `SizeResolver generates Size values`() { + val resolver = SizeResolver(Random.Default) + val result = resolver.resolve(typeOf(), testChain) + assertTrue(result is Size) + } + + @Test + fun `SizeResolver generates positive dimensions`() { + val resolver = SizeResolver(Random.Default) + repeat(100) { + val result = resolver.resolve(typeOf(), testChain) as Size + assertTrue(result.width >= 0f) + assertTrue(result.height >= 0f) + } + } + + @Test + fun `SizeResolver canResolve detects Size type`() { + val resolver = SizeResolver(Random.Default) + assertTrue(resolver.canResolve(typeOf())) + } + + @Test + fun `SizeResolver rejects non-Size types`() { + val resolver = SizeResolver(Random.Default) + assertFalse(resolver.canResolve(typeOf())) + assertFalse(resolver.canResolve(typeOf())) + } + + companion object { + private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/compose/SpResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/SpResolverTest.kt new file mode 100644 index 00000000..e94beb57 --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/SpResolverTest.kt @@ -0,0 +1,50 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp +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 SpResolverTest { + @Test + fun `SpResolver generates TextUnit values`() { + val resolver = SpResolver(Random.Default) + val result = resolver.resolve(typeOf(), testChain) + assertTrue(result is TextUnit) + assertTrue((result as TextUnit).isSp) + } + + @Test + fun `SpResolver generates values within range`() { + val resolver = SpResolver(Random.Default) + repeat(100) { + val result = resolver.resolve(typeOf(), testChain) as TextUnit + assertTrue(result.value in 0f..100f) + } + } + + @Test + fun `SpResolver canResolve detects TextUnit type`() { + val resolver = SpResolver(Random.Default) + assertTrue(resolver.canResolve(typeOf())) + } + + @Test + fun `SpResolver rejects non-TextUnit types`() { + val resolver = SpResolver(Random.Default) + assertFalse(resolver.canResolve(typeOf())) + assertFalse(resolver.canResolve(typeOf())) + } + + companion object { + private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) + } +} diff --git a/docs/android/compose.md b/docs/android/compose.md new file mode 100644 index 00000000..337af24e --- /dev/null +++ b/docs/android/compose.md @@ -0,0 +1,16 @@ +# Compose + +Support for Jetpack Compose unit and geometry types is available in the `some-android` module. + +## Supported Compose types + +| Type | Usage | Notes | +|------|-------|-------| +| `androidx.compose.ui.unit.Dp` | `some()` | Random float in `0f..1000f` | +| `androidx.compose.ui.unit.TextUnit` | `some()` | Random `sp` value in `0f..100f` | +| `androidx.compose.ui.geometry.Offset` | `some()` | Random float `x` and `y` | +| `androidx.compose.ui.geometry.Size` | `some()` | Random positive float `width` and `height` | +| `androidx.compose.ui.geometry.Rect` | `some()` | Random float bounds where `left < right` and `top < bottom` | +| `androidx.compose.ui.unit.IntOffset` | `some()` | Random integer `x` and `y` | +| `androidx.compose.ui.unit.IntSize` | `some()` | Random positive integer `width` and `height` | +| `androidx.compose.ui.text.AnnotatedString` | `some()` | Non-blank plain text without span styles | diff --git a/docs/android/index.md b/docs/android/index.md index f8102a73..b360978e 100644 --- a/docs/android/index.md +++ b/docs/android/index.md @@ -51,7 +51,12 @@ 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 | + +## Compose support + +`some-android` includes specialized support for Jetpack Compose types, including units and geometry. + +See [Compose](compose.md) for the full list of supported Compose types. ## Platform constraints diff --git a/docs/supported-types.md b/docs/supported-types.md index 7bd373f3..63ec434e 100644 --- a/docs/supported-types.md +++ b/docs/supported-types.md @@ -47,6 +47,9 @@ For types not listed here, register a [custom factory](custom-factories.md) or s | `Map` | `some>()` | | | `MutableMap` | `some>()` | | | `Array` | `some>()` | | +| **Android & Compose** | | | +| Android framework | `some()`, `some()`, ... | See [Android](android/index.md) | +| Jetpack Compose | `some()`, `some()`, ... | See [Compose](android/compose.md) | | **Kotlin constructs** | | | | Data classes | `some()` | | | Sealed classes/interfaces | `some()` | Random subclass, including `object` variants | diff --git a/zensical.toml b/zensical.toml index 9ff93647..d413f0f5 100644 --- a/zensical.toml +++ b/zensical.toml @@ -26,7 +26,8 @@ nav = [ "supported-types.md", { "Android" = [ "android/index.md", - "android/uri.md" + "android/uri.md", + "android/compose.md" ] }, "kotest.md", "custom-factories.md", From 0a18b54a71e52b088a095aba54061964f452a6e6 Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Tue, 7 Jul 2026 10:29:17 +0100 Subject: [PATCH 2/2] reorder resolvers --- .../some/android/AndroidResolverProvider.kt | 22 ++++++++-------- .../android/AndroidResolverProviderTest.kt | 25 ++++++++++--------- 2 files changed, 24 insertions(+), 23 deletions(-) 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 ca8b9045..f3650e05 100644 --- a/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt +++ b/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt @@ -42,26 +42,26 @@ class AndroidResolverProvider : ResolverProvider { random: Random ): List { return listOf( - UriResolver(strategyProvider, random), BundleResolver(random), - RectResolver(random), - RectFResolver(random), - PointResolver(random), + ColorResolver(random), + PairResolver(random), PointFResolver(random), - SizeResolver(random), + PointResolver(random), + RectFResolver(random), + RectResolver(random), SizeFResolver(random), - PairResolver(random), + SizeResolver(random), + UriResolver(strategyProvider, random), UserHandleResolver(random), - ColorResolver(random), AnnotatedStringResolver(random), ComposeColorResolver(strategyProvider, random), DpResolver(random), - SpResolver(random), + IntOffsetResolver(random), + IntSizeResolver(random), OffsetResolver(random), - ComposeSizeResolver(random), ComposeRectResolver(random), - IntOffsetResolver(random), - IntSizeResolver(random) + ComposeSizeResolver(random), + SpResolver(random), ) } } diff --git a/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt b/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt index e59868ce..13be6e10 100644 --- a/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt +++ b/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt @@ -32,27 +32,28 @@ class AndroidResolverProviderTest { assertEquals( listOf( - UriResolver::class, BundleResolver::class, - RectResolver::class, - RectFResolver::class, - PointResolver::class, + ColorResolver::class, + PairResolver::class, PointFResolver::class, - SizeResolver::class, + PointResolver::class, + RectFResolver::class, + RectResolver::class, SizeFResolver::class, - PairResolver::class, + SizeResolver::class, + UriResolver::class, UserHandleResolver::class, - ColorResolver::class, AnnotatedStringResolver::class, + ComposeColorResolver::class, DpResolver::class, - SpResolver::class, - OffsetResolver::class, - ComposeSizeResolver::class, - ComposeRectResolver::class, IntOffsetResolver::class, IntSizeResolver::class, - ComposeColorResolver::class, + OffsetResolver::class, + ComposeRectResolver::class, + ComposeSizeResolver::class, + SpResolver::class, ), + resolvers.map { it::class } ) }