From 859afd3ca8727bc82f3ba728f03f67c6c19d5784 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Wed, 1 Jul 2026 18:26:28 +0000 Subject: [PATCH 1/2] Implement BundleResolver in android module Co-authored-by: MessiasLima --- .../some/android/resolver/BundleResolver.kt | 73 ++++++++++++++ .../android/resolver/BundleResolverTest.kt | 94 +++++++++++++++++++ docs/android/bundle.md | 45 +++++++++ docs/android/index.md | 3 +- 4 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 android/src/main/java/dev/appoutlet/some/android/resolver/BundleResolver.kt create mode 100644 android/src/test/java/dev/appoutlet/some/android/resolver/BundleResolverTest.kt create mode 100644 docs/android/bundle.md diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/BundleResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/BundleResolver.kt new file mode 100644 index 00000000..6bcf4b6a --- /dev/null +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/BundleResolver.kt @@ -0,0 +1,73 @@ +package dev.appoutlet.some.android.resolver + +import android.os.Bundle +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_ENTRIES = 0 +private const val MAX_ENTRIES = 5 +private const val MIN_KEY_LENGTH = 1 +private const val MAX_KEY_LENGTH = 10 + +/** + * Resolves [Bundle] types by creating a new [Bundle] and populating it with + * random entries. + * + * Generated bundles contain 0 to 5 entries. Each entry has a non-empty string + * key and a value of type [String], [Int], [Long], [Float], [Double], or + * [Boolean]. + * + * @param random Random source used when generating entry count, keys, and + * values. + */ +class BundleResolver( + private val random: Random +) : Resolver { + private val valueGenerators: List<() -> Any> = listOf( + { generateString() }, + { random.nextInt() }, + { random.nextLong() }, + { random.nextFloat() }, + { random.nextDouble() }, + { random.nextBoolean() } + ) + + override fun canResolve(type: KType): Boolean = type == typeOf() + + override fun resolve(type: KType, chain: ResolverChain): Any { + val bundle = Bundle() + val entryCount = random.nextInt(MIN_ENTRIES, MAX_ENTRIES + 1) + + repeat(entryCount) { + val key = generateKey() + val value = valueGenerators.random(random).invoke() + putValue(bundle, key, value) + } + + return bundle + } + + private fun generateKey(): String { + val length = random.nextInt(MIN_KEY_LENGTH, MAX_KEY_LENGTH + 1) + return List(length) { ('a'..'z').random(random) }.joinToString("") + } + + private fun generateString(): String { + val length = random.nextInt(MIN_KEY_LENGTH, MAX_KEY_LENGTH + 1) + return List(length) { ('a'..'z').random(random) }.joinToString("") + } + + private fun putValue(bundle: Bundle, key: String, value: Any) { + when (value) { + is String -> bundle.putString(key, value) + is Int -> bundle.putInt(key, value) + is Long -> bundle.putLong(key, value) + is Float -> bundle.putFloat(key, value) + is Double -> bundle.putDouble(key, value) + is Boolean -> bundle.putBoolean(key, value) + } + } +} diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/BundleResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/BundleResolverTest.kt new file mode 100644 index 00000000..4be6dcb4 --- /dev/null +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/BundleResolverTest.kt @@ -0,0 +1,94 @@ +package dev.appoutlet.some.android.resolver + +import android.os.Bundle +import dev.appoutlet.some.config.NullableStrategy +import dev.appoutlet.some.core.ResolverChain +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +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 BundleResolverTest { + @Test + fun `BundleResolver canResolve detects Bundle type`() { + val resolver = BundleResolver(Random.Default) + assertTrue(resolver.canResolve(typeOf())) + } + + @Test + fun `BundleResolver rejects non-Bundle types`() { + val resolver = BundleResolver(Random.Default) + assertFalse(resolver.canResolve(typeOf())) + assertFalse(resolver.canResolve(typeOf())) + } + + @Test + fun `BundleResolver generates non-null Bundle`() { + val resolver = BundleResolver(Random.Default) + val result = resolver.resolve(typeOf(), testChain) + assertNotNull(result) + assertTrue(result is Bundle) + } + + @Test + fun `BundleResolver generates bundles with 0 to 5 entries`() { + val resolver = BundleResolver(Random.Default) + + repeat(20) { + val result = resolver.resolve(typeOf(), testChain) as Bundle + assertTrue( + "Expected 0 to 5 entries but got ${result.size()}", + result.size() in MIN_ENTRY_COUNT..MAX_ENTRY_COUNT + ) + } + } + + @Test + fun `BundleResolver keys are non-empty`() { + val resolver = BundleResolver(Random.Default) + + repeat(20) { + val result = resolver.resolve(typeOf(), testChain) as Bundle + result.keySet().forEach { key -> + assertTrue("Key should not be empty", key.isNotEmpty()) + } + } + } + + @Test + fun `BundleResolver values are valid types`() { + val resolver = BundleResolver(Random.Default) + + repeat(20) { + val result = resolver.resolve(typeOf(), testChain) as Bundle + result.keySet().forEach { key -> + val value = result.getValue(key) + assertNotNull(value) + assertTrue( + "Unexpected value type: ${value!!.javaClass}", + value is String || + value is Int || + value is Long || + value is Float || + value is Double || + value is Boolean + ) + } + } + } + + @Suppress("DEPRECATION") + private fun Bundle.getValue(key: String): Any? = get(key) + + companion object { + private const val MIN_ENTRY_COUNT = 0 + private const val MAX_ENTRY_COUNT = 5 + + private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) + } +} diff --git a/docs/android/bundle.md b/docs/android/bundle.md new file mode 100644 index 00000000..9e738d04 --- /dev/null +++ b/docs/android/bundle.md @@ -0,0 +1,45 @@ +--- +icon: lucide/package +--- + +# Bundle + +`some-android` includes `BundleResolver` for generating `android.os.Bundle` instances with random entries. + +## Generated bundles + +`BundleResolver` creates a new `Bundle()` and populates it with: + +- 0 to 5 entries +- Non-empty string keys +- Values of type `String`, `Int`, `Long`, `Float`, `Double`, or `Boolean` + +Entry count, keys, and value types are all chosen randomly. + +## Direct usage + +```kotlin +import android.os.Bundle +import dev.appoutlet.some.android.resolver.BundleResolver +import dev.appoutlet.some.config.NullableStrategy +import dev.appoutlet.some.core.ResolverChain +import kotlin.random.Random +import kotlin.reflect.typeOf + +val resolver = BundleResolver(Random.Default) +val chain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) +val bundle = resolver.resolve(typeOf(), chain) as Bundle +``` + +## Value types + +Each entry uses one of the supported `Bundle` primitive methods: + +- `putString` +- `putInt` +- `putLong` +- `putFloat` +- `putDouble` +- `putBoolean` + +See [Android](index.md) for installation and the list of Android-specific supported types. diff --git a/docs/android/index.md b/docs/android/index.md index bfab06bf..304f6116 100644 --- a/docs/android/index.md +++ b/docs/android/index.md @@ -36,11 +36,12 @@ 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 one Android-specific type: +Today, the Android module adds support for the following Android-specific types: | Type | Usage | Notes | |------|-------|-------| | `android.net.Uri` | `some()` | See [Uri](uri.md) and [Uri Strategy](uri.md#uri-strategy) | +| `android.os.Bundle` | `BundleResolver` | See [Bundle](bundle.md) | ## Platform constraints From 03c9893afc26ff92d6cf9b955ecec6950f852833 Mon Sep 17 00:00:00 2001 From: Messias Junior Date: Mon, 6 Jul 2026 17:00:32 +0100 Subject: [PATCH 2/2] simplify Bundle Resolver --- .../some/android/resolver/BundleResolver.kt | 59 +++++-------------- .../android/resolver/BundleResolverTest.kt | 53 ++++------------- docs/android/bundle.md | 45 -------------- docs/android/index.md | 2 +- 4 files changed, 27 insertions(+), 132 deletions(-) delete mode 100644 docs/android/bundle.md diff --git a/android/src/main/java/dev/appoutlet/some/android/resolver/BundleResolver.kt b/android/src/main/java/dev/appoutlet/some/android/resolver/BundleResolver.kt index 6bcf4b6a..6b049cc2 100644 --- a/android/src/main/java/dev/appoutlet/some/android/resolver/BundleResolver.kt +++ b/android/src/main/java/dev/appoutlet/some/android/resolver/BundleResolver.kt @@ -7,67 +7,36 @@ import kotlin.random.Random import kotlin.reflect.KType import kotlin.reflect.typeOf -private const val MIN_ENTRIES = 0 -private const val MAX_ENTRIES = 5 -private const val MIN_KEY_LENGTH = 1 -private const val MAX_KEY_LENGTH = 10 +private const val MIN_STRING_LENGTH = 1 +private const val MAX_STRING_LENGTH = 10 /** * Resolves [Bundle] types by creating a new [Bundle] and populating it with - * random entries. + * one entry for each supported primitive value type. * - * Generated bundles contain 0 to 5 entries. Each entry has a non-empty string - * key and a value of type [String], [Int], [Long], [Float], [Double], or - * [Boolean]. + * Generated bundles always contain one [String], [Int], [Long], [Float], + * [Double], and [Boolean] entry with random values. * - * @param random Random source used when generating entry count, keys, and - * values. + * @param random Random source used when generating values. */ class BundleResolver( private val random: Random ) : Resolver { - private val valueGenerators: List<() -> Any> = listOf( - { generateString() }, - { random.nextInt() }, - { random.nextLong() }, - { random.nextFloat() }, - { random.nextDouble() }, - { random.nextBoolean() } - ) - override fun canResolve(type: KType): Boolean = type == typeOf() override fun resolve(type: KType, chain: ResolverChain): Any { - val bundle = Bundle() - val entryCount = random.nextInt(MIN_ENTRIES, MAX_ENTRIES + 1) - - repeat(entryCount) { - val key = generateKey() - val value = valueGenerators.random(random).invoke() - putValue(bundle, key, value) + return Bundle().apply { + putString("string", generateString()) + putInt("int", random.nextInt()) + putLong("long", random.nextLong()) + putFloat("float", random.nextFloat()) + putDouble("double", random.nextDouble()) + putBoolean("boolean", random.nextBoolean()) } - - return bundle - } - - private fun generateKey(): String { - val length = random.nextInt(MIN_KEY_LENGTH, MAX_KEY_LENGTH + 1) - return List(length) { ('a'..'z').random(random) }.joinToString("") } private fun generateString(): String { - val length = random.nextInt(MIN_KEY_LENGTH, MAX_KEY_LENGTH + 1) + val length = random.nextInt(MIN_STRING_LENGTH, MAX_STRING_LENGTH + 1) return List(length) { ('a'..'z').random(random) }.joinToString("") } - - private fun putValue(bundle: Bundle, key: String, value: Any) { - when (value) { - is String -> bundle.putString(key, value) - is Int -> bundle.putInt(key, value) - is Long -> bundle.putLong(key, value) - is Float -> bundle.putFloat(key, value) - is Double -> bundle.putDouble(key, value) - is Boolean -> bundle.putBoolean(key, value) - } - } } diff --git a/android/src/test/java/dev/appoutlet/some/android/resolver/BundleResolverTest.kt b/android/src/test/java/dev/appoutlet/some/android/resolver/BundleResolverTest.kt index 4be6dcb4..77c6bb6f 100644 --- a/android/src/test/java/dev/appoutlet/some/android/resolver/BundleResolverTest.kt +++ b/android/src/test/java/dev/appoutlet/some/android/resolver/BundleResolverTest.kt @@ -3,6 +3,7 @@ package dev.appoutlet.some.android.resolver import android.os.Bundle import dev.appoutlet.some.config.NullableStrategy import dev.appoutlet.some.core.ResolverChain +import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue @@ -36,49 +37,22 @@ class BundleResolverTest { } @Test - fun `BundleResolver generates bundles with 0 to 5 entries`() { + fun `BundleResolver generates one entry for each supported type`() { val resolver = BundleResolver(Random.Default) repeat(20) { val result = resolver.resolve(typeOf(), testChain) as Bundle - assertTrue( - "Expected 0 to 5 entries but got ${result.size()}", - result.size() in MIN_ENTRY_COUNT..MAX_ENTRY_COUNT - ) - } - } - - @Test - fun `BundleResolver keys are non-empty`() { - val resolver = BundleResolver(Random.Default) - - repeat(20) { - val result = resolver.resolve(typeOf(), testChain) as Bundle - result.keySet().forEach { key -> - assertTrue("Key should not be empty", key.isNotEmpty()) - } - } - } - - @Test - fun `BundleResolver values are valid types`() { - val resolver = BundleResolver(Random.Default) - repeat(20) { - val result = resolver.resolve(typeOf(), testChain) as Bundle - result.keySet().forEach { key -> - val value = result.getValue(key) - assertNotNull(value) - assertTrue( - "Unexpected value type: ${value!!.javaClass}", - value is String || - value is Int || - value is Long || - value is Float || - value is Double || - value is Boolean - ) - } + assertEquals(6, result.size()) + assertEquals(setOf("string", "int", "long", "float", "double", "boolean"), result.keySet()) + assertNotNull(result.getString("string")) + assertTrue(result.getString("string")!!.isNotEmpty()) + assertTrue(result.getValue("string") is String) + assertTrue(result.getValue("int") is Int) + assertTrue(result.getValue("long") is Long) + assertTrue(result.getValue("float") is Float) + assertTrue(result.getValue("double") is Double) + assertTrue(result.getValue("boolean") is Boolean) } } @@ -86,9 +60,6 @@ class BundleResolverTest { private fun Bundle.getValue(key: String): Any? = get(key) companion object { - private const val MIN_ENTRY_COUNT = 0 - private const val MAX_ENTRY_COUNT = 5 - private val testChain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) } } diff --git a/docs/android/bundle.md b/docs/android/bundle.md deleted file mode 100644 index 9e738d04..00000000 --- a/docs/android/bundle.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -icon: lucide/package ---- - -# Bundle - -`some-android` includes `BundleResolver` for generating `android.os.Bundle` instances with random entries. - -## Generated bundles - -`BundleResolver` creates a new `Bundle()` and populates it with: - -- 0 to 5 entries -- Non-empty string keys -- Values of type `String`, `Int`, `Long`, `Float`, `Double`, or `Boolean` - -Entry count, keys, and value types are all chosen randomly. - -## Direct usage - -```kotlin -import android.os.Bundle -import dev.appoutlet.some.android.resolver.BundleResolver -import dev.appoutlet.some.config.NullableStrategy -import dev.appoutlet.some.core.ResolverChain -import kotlin.random.Random -import kotlin.reflect.typeOf - -val resolver = BundleResolver(Random.Default) -val chain = ResolverChain(emptyList(), NullableStrategy.NullOnCircularReference) -val bundle = resolver.resolve(typeOf(), chain) as Bundle -``` - -## Value types - -Each entry uses one of the supported `Bundle` primitive methods: - -- `putString` -- `putInt` -- `putLong` -- `putFloat` -- `putDouble` -- `putBoolean` - -See [Android](index.md) for installation and the list of Android-specific supported types. diff --git a/docs/android/index.md b/docs/android/index.md index bfeab913..1934bfe6 100644 --- a/docs/android/index.md +++ b/docs/android/index.md @@ -47,7 +47,7 @@ Today, the Android module adds these Android-specific types: | `android.graphics.PointF` | `some()` | Random float `x` and `y` | | `android.util.Size` | `some()` | Random positive integer `width` and `height` | | `android.util.SizeF` | `some()` | Random positive float `width` and `height` | -| `android.os.Bundle` | `some` | See [Bundle](bundle.md) | +| `android.os.Bundle` | `some()` | One random value each for `String`, `Int`, `Long`, `Float`, `Double`, and `Boolean` | ## Platform constraints