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 77d90341..f3650e05 100644 --- a/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt +++ b/android/src/main/java/dev/appoutlet/some/android/AndroidResolverProvider.kt @@ -12,11 +12,18 @@ 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.SpResolver import dev.appoutlet.some.core.Resolver import dev.appoutlet.some.core.ResolverProvider import dev.appoutlet.some.core.StrategyProvider import kotlin.random.Random import dev.appoutlet.some.android.resolver.compose.ColorResolver as ComposeColorResolver +import dev.appoutlet.some.android.resolver.compose.RectResolver as ComposeRectResolver +import dev.appoutlet.some.android.resolver.compose.SizeResolver as ComposeSizeResolver /** * Contributes Android-specific resolvers discovered through `ServiceLoader`. @@ -35,19 +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), + IntOffsetResolver(random), + IntSizeResolver(random), + OffsetResolver(random), + ComposeRectResolver(random), + ComposeSizeResolver(random), + SpResolver(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 c8dbd490..13be6e10 100644 --- a/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt +++ b/android/src/test/java/dev/appoutlet/some/android/AndroidResolverProviderTest.kt @@ -12,11 +12,18 @@ 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.SpResolver import dev.appoutlet.some.config.DefaultStrategyProvider import org.junit.Assert.assertEquals import org.junit.Test import kotlin.random.Random import dev.appoutlet.some.android.resolver.compose.ColorResolver as ComposeColorResolver +import dev.appoutlet.some.android.resolver.compose.RectResolver as ComposeRectResolver +import dev.appoutlet.some.android.resolver.compose.SizeResolver as ComposeSizeResolver class AndroidResolverProviderTest { @Test @@ -25,20 +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, + IntOffsetResolver::class, + IntSizeResolver::class, + OffsetResolver::class, + ComposeRectResolver::class, + ComposeSizeResolver::class, + SpResolver::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 2664e29f..4328d280 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 @@ -31,4 +38,15 @@ class AndroidServiceLoaderTest { val result: ComposeColor = some() assertTrue(result.alpha in 0f..1f) } + + @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..a5570b01 --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/compose/SpResolverTest.kt @@ -0,0 +1,49 @@ +package dev.appoutlet.some.android.resolver.compose + +import androidx.compose.ui.unit.TextUnit +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..9ac57506 --- /dev/null +++ b/docs/android/compose.md @@ -0,0 +1,19 @@ +# Compose + +Support for Jetpack Compose unit, geometry, text, and color 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 | +| `androidx.compose.ui.graphics.Color` | `some()` | See [Compose Color](color.md) and [Color Strategy](color.md#color-strategy) | + +See [Android](index.md) for installation and the rest of the Android-specific supported types. diff --git a/docs/android/index.md b/docs/android/index.md index f5dd1719..fc5d4c59 100644 --- a/docs/android/index.md +++ b/docs/android/index.md @@ -36,7 +36,7 @@ Use `dev.appoutlet:some-android` in Android projects when you want the shared So All shared types from [Supported Types](../supported-types.md) are available here too. -Today, the Android module adds these Android-specific types: +Today, the Android module adds these Android platform types: | Type | Usage | Notes | |------|------------------|-------| @@ -51,8 +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 | -| `androidx.compose.ui.graphics.Color` | `some()` | See [Compose Color](color.md) and [Color Strategy](color.md#color-strategy) | + +## Compose support + +`some-android` includes specialized support for Jetpack Compose types, including units, geometry, text, and color. + +See [Compose](compose.md) for the full list of supported Compose types, including `AnnotatedString` and Compose `Color`. ## 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 88f6c995..93ae2b65 100644 --- a/zensical.toml +++ b/zensical.toml @@ -27,6 +27,7 @@ nav = [ { "Android" = [ "android/index.md", "android/uri.md", + "android/compose.md", "android/color.md" ] }, "kotest.md",