Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,50 @@
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.core.Resolver
import dev.appoutlet.some.core.ResolverProvider
import dev.appoutlet.some.core.StrategyProvider
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`.
*/
class AndroidResolverProvider : ResolverProvider {
/**
* Creates the Android resolver set using the shared [strategyProvider] and [random] source.
*
* Resolvers that depend on strategies receive [strategyProvider]; all others share the same [random]
* instance so generated Android values stay consistent with the rest of the fixture session.
*/
override fun createResolvers(
strategyProvider: StrategyProvider,
random: Random
): List<Resolver> {
return emptyList()
return listOf(
UriResolver(strategyProvider, random),
BundleResolver(random),
RectResolver(random),
RectFResolver(random),
PointResolver(random),
PointFResolver(random),
SizeResolver(random),
SizeFResolver(random),
PairResolver(random),
UserHandleResolver(random),
ColorResolver(random)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dev.appoutlet.some.android.resolver

import android.graphics.Color
import dev.appoutlet.some.core.Resolver
import dev.appoutlet.some.core.ResolverChain
import kotlin.random.Random
import kotlin.reflect.KType
import kotlin.reflect.typeOf

/**
* Resolves [Color] values when the runtime API level supports [Color.valueOf].
*
* @param random Random source used when generating RGB channel values.
*/
class ColorResolver(
private val random: Random
) : Resolver {
Comment thread
MessiasLima marked this conversation as resolved.
override fun canResolve(type: KType): Boolean = type == typeOf<Color>()

override fun resolve(type: KType, chain: ResolverChain): Color {
return Color.valueOf(
random.nextFloat(),
random.nextFloat(),
random.nextFloat(),
)
}
Comment thread
MessiasLima marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package dev.appoutlet.some.android.resolver

import android.util.Pair
import dev.appoutlet.some.core.Resolver
import dev.appoutlet.some.core.ResolverChain
import kotlin.random.Random
import kotlin.reflect.KType

/**
* Resolves [Pair] types by delegating both generic type arguments back through the resolver chain.
*
* @param random Shared random source accepted for consistency with the resolver provider API.
*/
class PairResolver(
@Suppress("UNUSED_PARAMETER")
random: Random
) : Resolver {
Comment thread
MessiasLima marked this conversation as resolved.
override fun canResolve(type: KType): Boolean = type.classifier == Pair::class

override fun resolve(type: KType, chain: ResolverChain): Any {
val firstType = requireNotNull(type.arguments.getOrNull(0)?.type) {
"Star projection not supported in Pair first type"
}
val secondType = requireNotNull(type.arguments.getOrNull(1)?.type) {
"Star projection not supported in Pair second type"
}

return Pair(chain.resolve(firstType), chain.resolve(secondType))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.appoutlet.some.android.resolver

import android.os.UserHandle
import dev.appoutlet.some.core.Resolver
import dev.appoutlet.some.core.ResolverChain
import kotlin.random.Random
import kotlin.reflect.KType
import kotlin.reflect.typeOf

/**
* Resolves [UserHandle] values when the runtime API level supports [UserHandle.getUserHandleForUid].
*
* @param random Random source used when generating a non-system application UID.
*/
class UserHandleResolver(
private val random: Random
) : Resolver {
Comment thread
MessiasLima marked this conversation as resolved.
override fun canResolve(type: KType): Boolean = type == typeOf<UserHandle>()

override fun resolve(type: KType, chain: ResolverChain): Any? {
return UserHandle.getUserHandleForUid(random.nextInt())
}
Comment thread
MessiasLima marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.appoutlet.some.android

import android.graphics.Point
import dev.appoutlet.some.some
import org.junit.Assert.assertTrue
import org.junit.Test
Expand All @@ -10,4 +11,10 @@ class AndroidServiceLoaderTest {
val result: String = some()
assertTrue("Generated string should not be empty", result.isNotEmpty())
}

@Test
fun `top-level some resolves Android types through AndroidResolverProvider`() {
val result: Any = some<Point>()
assertTrue(result is Point)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.appoutlet.some.android.resolver

import android.graphics.Color
import android.os.UserHandle
import android.util.Pair
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 org.robolectric.annotation.Config
import kotlin.random.Random
import kotlin.reflect.typeOf

@RunWith(RobolectricTestRunner::class)
class ColorResolverTest {
@Test
@Config(sdk = [26])
fun `ColorResolver generates Color values`() {
val resolver = ColorResolver(Random.Default)
val result = resolver.resolve(typeOf<Color>(), testChain)
assertTrue(result is Color)
}
Comment thread
MessiasLima marked this conversation as resolved.

@Test
fun `ColorResolver canResolve detects Color type`() {
val resolver = ColorResolver(Random.Default)
assertTrue(resolver.canResolve(typeOf<Color>()))
}

@Test
fun `ColorResolver rejects non-Color types`() {
val resolver = ColorResolver(Random.Default)
assertFalse(resolver.canResolve(typeOf<String>()))
assertFalse(resolver.canResolve(typeOf<Int>()))
assertFalse(resolver.canResolve(typeOf<UserHandle>()))
assertFalse(resolver.canResolve(typeOf<Pair<String, Int>>()))
}

companion object {
private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package dev.appoutlet.some.android.resolver

import android.graphics.Color
import android.os.UserHandle
import android.util.Pair
import dev.appoutlet.some.config.NullableStrategy
import dev.appoutlet.some.core.Resolver
import dev.appoutlet.some.core.ResolverChain
import org.junit.Assert.assertEquals
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.KType
import kotlin.reflect.typeOf

@RunWith(RobolectricTestRunner::class)
class PairResolverTest {
@Test
fun `PairResolver generates Pair values by delegating both generic types to the chain`() {
val resolver = PairResolver(Random.Default)
val resolvedTypes = mutableListOf<KType>()
val chain = ResolverChain(
listOf(
TrackingResolver(typeOf<String>(), "first-value", resolvedTypes),
TrackingResolver(typeOf<Int>(), 42, resolvedTypes)
),
NullableStrategy.NullOnCircularReference
)

val result = resolver.resolve(typeOf<Pair<String, Int>>(), chain) as Pair<*, *>

assertEquals("first-value", result.first)
assertEquals(42, result.second)
assertEquals(listOf(typeOf<String>(), typeOf<Int>()), resolvedTypes)
}

@Test
fun `PairResolver canResolve detects Pair type`() {
val resolver = PairResolver(Random.Default)
assertTrue(resolver.canResolve(typeOf<Pair<String, Int>>()))
}

@Test
fun `PairResolver rejects non-Pair types`() {
val resolver = PairResolver(Random.Default)
assertFalse(resolver.canResolve(typeOf<String>()))
assertFalse(resolver.canResolve(typeOf<Int>()))
assertFalse(resolver.canResolve(typeOf<Color>()))
assertFalse(resolver.canResolve(typeOf<UserHandle>()))
assertFalse(resolver.canResolve(typeOf<kotlin.Pair<String, Int>>()))
}

private class TrackingResolver(
private val supportedType: KType,
private val value: Any,
private val resolvedTypes: MutableList<KType>
) : Resolver {
override fun canResolve(type: KType): Boolean = type == supportedType

override fun resolve(type: KType, chain: ResolverChain): Any {
resolvedTypes += type
return value
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.appoutlet.some.android.resolver

import android.graphics.Color
import android.os.UserHandle
import android.util.Pair
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 org.robolectric.annotation.Config
import kotlin.random.Random
import kotlin.reflect.typeOf

@RunWith(RobolectricTestRunner::class)
class UserHandleResolverTest {
@Test
@Config(sdk = [26])
fun `UserHandleResolver generates UserHandle values when API level is supported`() {
val resolver = UserHandleResolver(Random.Default)
val result = resolver.resolve(typeOf<UserHandle>(), testChain)
assertTrue(result is UserHandle)
}
Comment thread
MessiasLima marked this conversation as resolved.

@Test
fun `UserHandleResolver canResolve detects UserHandle type`() {
val resolver = UserHandleResolver(Random.Default)
assertTrue(resolver.canResolve(typeOf<UserHandle>()))
}

@Test
fun `UserHandleResolver rejects non-UserHandle types`() {
val resolver = UserHandleResolver(Random.Default)
assertFalse(resolver.canResolve(typeOf<String>()))
assertFalse(resolver.canResolve(typeOf<Int>()))
assertFalse(resolver.canResolve(typeOf<Color>()))
assertFalse(resolver.canResolve(typeOf<Pair<String, Int>>()))
}

companion object {
private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference)
}
}
5 changes: 4 additions & 1 deletion docs/android/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ Today, the Android module adds these Android-specific types:
| `android.graphics.PointF` | `some<PointF>()` | Random float `x` and `y` |
| `android.util.Size` | `some<Size>()` | Random positive integer `width` and `height` |
| `android.util.SizeF` | `some<SizeF>()` | Random positive float `width` and `height` |
| `android.util.Pair<F, S>` | `some<Pair<String, Int>>()` | Resolves `first` and `second` by delegating both generic type arguments through the resolver chain |
| `android.os.Bundle` | `some<Bundle>()` | One random value each for `String`, `Int`, `Long`, `Float`, `Double`, and `Boolean` |
| `android.os.UserHandle` | `some<UserHandle>()` | API 24+; generated from a random non-system UID |
| `android.graphics.Color` | `some<Color>()` | API 26+; generated from random RGB channel values |

## Platform constraints

- Android `minSdk 24`
- Android `minSdk 26`
Comment thread
MessiasLima marked this conversation as resolved.
- Shared API behavior follows the same Kotlin/JVM constraints as `some-core`
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,4 @@ For more configuration use cases and details, see the [Configuration overview](c

- **Kotlin 2.3.0+** — required for `typeOf<T>()` and other type introspection APIs
- **JVM 17+** — `some-core` targets JVM 17+
- **Android minSdk 24** — use `some-android` for Android projects
- **Android minSdk 26** — use `some-android` for Android projects
Comment thread
MessiasLima marked this conversation as resolved.