Add tests for CollectionNavType

Test: ./gradlew navigation:navigation-common:cC
Test: ./gradlew navigation:navigation-common:test
Test: ./gradlew navigation:navigation-runtime:cC
Bug: 188693139
Change-Id: I9bb8e49046d7c8335e979f61044b4058653ecad4
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteDecoderTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteDecoderTest.kt
index c07082f..6905a17 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteDecoderTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteDecoderTest.kt
@@ -379,6 +379,17 @@
         assertThat(result.custom.arg).isNull()
     }
 
+    @Test
+    fun decodeCollectionNavType() {
+        val arg = listOf(CustomType(1), CustomType(3), CustomType(5))
+        val bundle = bundleOf("list" to arg)
+        val result = decode<TestClassCollectionArg>(
+            bundle,
+            listOf(navArgument("list") { type = collectionNavType })
+        )
+        assertThat(result.list).containsExactlyElementsIn(arg).inOrder()
+    }
+
     private inline fun <reified T : Any> decode(
         bundle: Bundle,
         args: List<NamedNavArgument> = emptyList()
diff --git a/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteFilledTest.kt b/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteFilledTest.kt
index 6563a2b..21f2e2e 100644
--- a/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteFilledTest.kt
+++ b/navigation/navigation-common/src/androidTest/java/androidx/navigation/serialization/RouteFilledTest.kt
@@ -17,6 +17,7 @@
 package androidx.navigation.serialization
 
 import android.os.Bundle
+import androidx.navigation.CollectionNavType
 import androidx.navigation.NamedNavArgument
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
@@ -745,6 +746,16 @@
             PATH_SERIAL_NAME
         )
     }
+
+    @Test
+    fun collectionNavType() {
+        assertThatRouteFilledFrom(
+            TestClassCollectionArg(listOf(CustomType(1), CustomType(3), CustomType(5))),
+            listOf(navArgument("list") { type = collectionNavType })
+        ).isEqualTo(
+            "$PATH_SERIAL_NAME?list=1&list=3&list=5"
+        )
+    }
 }
 
 private fun <T : Any> assertThatRouteFilledFrom(
@@ -809,6 +820,25 @@
         CustomSerializerClass(decoder.decodeLong())
 }
 
+@Serializable
+data class CustomType(val id: Int)
+
+@Serializable
+@SerialName(PATH_SERIAL_NAME)
+class TestClassCollectionArg(val list: List<CustomType>)
+
+val collectionNavType = object : CollectionNavType<List<CustomType>>(false) {
+    override fun put(bundle: Bundle, key: String, value: List<CustomType>) { }
+    override fun serializeAsValues(value: List<CustomType>): List<String> =
+        value.map { it.id.toString() }
+    @Suppress("UNCHECKED_CAST", "DEPRECATION")
+    override fun get(bundle: Bundle, key: String): List<CustomType> {
+        return bundle[key] as List<CustomType>
+    }
+    override fun parseValue(value: String): List<CustomType> = listOf()
+    override fun serializeAsValue(value: List<CustomType>) = "customValue"
+}
+
 private interface TestInterface
 
 internal fun stringArgument(
diff --git a/navigation/navigation-common/src/test/java/androidx/navigation/serialization/RoutePatternTest.kt b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/RoutePatternTest.kt
index 41e24f8..dee4366 100644
--- a/navigation/navigation-common/src/test/java/androidx/navigation/serialization/RoutePatternTest.kt
+++ b/navigation/navigation-common/src/test/java/androidx/navigation/serialization/RoutePatternTest.kt
@@ -17,6 +17,7 @@
 package androidx.navigation.serialization
 
 import android.os.Bundle
+import androidx.navigation.CollectionNavType
 import androidx.navigation.NavType
 import com.google.common.truth.Truth.assertThat
 import kotlin.reflect.KType
@@ -512,6 +513,28 @@
             PATH_SERIAL_NAME
         )
     }
+
+    @Test
+    fun collectionNavType() {
+        @Serializable
+        class CustomType
+
+        @Serializable
+        @SerialName(PATH_SERIAL_NAME)
+        class TestClass(val list: List<CustomType>)
+
+        val type = object : CollectionNavType<List<CustomType>>(false) {
+            override fun put(bundle: Bundle, key: String, value: List<CustomType>) { }
+            override fun serializeAsValues(value: List<CustomType>): List<String> = emptyList()
+            override fun get(bundle: Bundle, key: String): List<CustomType>? = null
+            override fun parseValue(value: String): List<CustomType> = listOf()
+            override fun serializeAsValue(value: List<CustomType>) = "customValue"
+        }
+        val map = mapOf(typeOf<List<CustomType>>() to type)
+        assertThatRoutePatternFrom(serializer<TestClass>(), map).isEqualTo(
+            "$PATH_SERIAL_NAME?list={list}"
+        )
+    }
 }
 
 private fun <T> assertThatRoutePatternFrom(
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
index 07069ca..e7de37a 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -54,6 +54,7 @@
 import com.google.common.truth.Truth.assertWithMessage
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
+import kotlin.reflect.typeOf
 import kotlin.test.assertFailsWith
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
@@ -261,7 +262,7 @@
 
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = TestClass::class) {
-            test(route = TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("test")
         assertThat(navController.currentDestination?.id).isEqualTo(
@@ -278,7 +279,7 @@
 
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = TestClass::class) {
-            test(route = TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("test/{arg}")
         assertThat(navController.currentDestination?.id).isEqualTo(
@@ -301,7 +302,7 @@
             startDestination = NestedGraph::class
         ) {
             navigation<NestedGraph>(startDestination = TestClass::class) {
-                test(route = TestClass::class)
+                test<TestClass>()
             }
         }
         assertThat(navController.currentDestination?.route).isEqualTo("test")
@@ -319,7 +320,7 @@
 
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = TestClass()) {
-            test(route = TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("test")
         assertThat(navController.currentDestination?.id).isEqualTo(
@@ -338,7 +339,7 @@
         navController.graph = navController.createGraph(
             startDestination = TestClass(0)
         ) {
-            test(route = TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo(
             "test/{arg}"
@@ -362,7 +363,7 @@
         navController.graph = navController.createGraph(
             startDestination = TestClass(false)
         ) {
-            test(route = TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo(
             "test?arg={arg}"
@@ -467,7 +468,7 @@
             startDestination = NestedGraph(0)
         ) {
             navigation<NestedGraph>(startDestination = TestClass(1)) {
-                test(route = TestClass::class)
+                test<TestClass>()
             }
         }
         assertThat(navController.currentDestination?.route).isEqualTo(
@@ -710,7 +711,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("start")
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -726,7 +727,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("start")
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -747,7 +748,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("start")
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -771,7 +772,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("start")
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -790,6 +791,56 @@
 
     @UiThreadTest
     @Test
+    fun testNavigateWithObjectCollectionNavType() {
+        val navController = createNavController()
+        @Serializable
+        data class CustomType(val id: Int)
+        @Serializable
+        class TestClass(val list: List<CustomType>)
+
+        val collectionNavType = object : CollectionNavType<List<CustomType>>(false) {
+            override fun put(bundle: Bundle, key: String, value: List<CustomType>) {
+                val array = value.map { it.id }.toIntArray()
+                bundle.putIntArray(key, array)
+            }
+            override fun serializeAsValues(value: List<CustomType>) = value.map { it.id.toString() }
+            override fun get(bundle: Bundle, key: String): List<CustomType> {
+                return bundle.getIntArray(key)!!.map { CustomType(it) }
+            }
+            override fun parseValue(value: String) = listOf(CustomType(value.toInt()))
+            override fun parseValue(
+                value: String,
+                previousValue: List<CustomType>
+            ): List<CustomType> {
+                val list = mutableListOf<CustomType>()
+                list.addAll(previousValue)
+                list.add(CustomType(value.toInt()))
+                return list
+            }
+        }
+
+        navController.graph = navController.createGraph(startDestination = "start") {
+            test("start")
+            test<TestClass>(mapOf(typeOf<List<CustomType>>() to collectionNavType))
+        }
+
+        assertThat(navController.currentDestination?.route).isEqualTo("start")
+        assertThat(navController.currentBackStack.value.size).isEqualTo(2)
+
+        val list = listOf(CustomType(1), CustomType(3), CustomType(5))
+        navController.navigate(TestClass(list))
+        assertThat(navController.currentDestination?.route).isEqualTo(
+            "androidx.navigation.NavControllerRouteTest." +
+                "testNavigateWithObjectCollectionNavType.TestClass?list={list}"
+        )
+        assertThat(navController.currentBackStack.value.size).isEqualTo(3)
+        assertThat(
+            navController.currentBackStackEntry!!.toRoute<TestClass>().list
+        ).containsExactlyElementsIn(list).inOrder()
+    }
+
+    @UiThreadTest
+    @Test
     fun testNavigateWithObjectInvalidObject() {
         @Serializable
         class WrongTestClass
@@ -797,7 +848,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("start")
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -813,7 +864,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("start")
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -831,7 +882,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("start")
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -862,7 +913,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
         assertThat(navController.currentDestination?.route).isEqualTo("start")
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -1403,7 +1454,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         navController.navigate(TEST_CLASS_ROUTE)
@@ -1421,7 +1472,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
 
         navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
@@ -1439,7 +1490,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         navController.navigate(TEST_CLASS_ROUTE)
@@ -1459,7 +1510,7 @@
             route = TestGraph::class,
             startDestination = TestClass::class
         ) {
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -1479,7 +1530,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
 
         navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
@@ -1497,7 +1548,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
 
         navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
@@ -1727,7 +1778,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         // first nav
@@ -1752,7 +1803,7 @@
             route = TestGraph::class,
             startDestination = TestClass::class
         ) {
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -1773,7 +1824,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
 
         // first nav
@@ -1796,7 +1847,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         // first nav
@@ -1821,7 +1872,7 @@
             route = TestGraph::class,
             startDestination = TestClass::class
         ) {
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         assertThat(navController.currentBackStack.value.size).isEqualTo(2)
@@ -1842,7 +1893,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
 
         // first nav
@@ -1865,7 +1916,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
 
         // first nav
@@ -2217,7 +2268,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         navController.navigate(TEST_CLASS_ROUTE)
@@ -2236,7 +2287,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         navController.navigate(TEST_CLASS_ROUTE)
@@ -2255,7 +2306,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
 
         navController.navigate(TEST_CLASS_PATH_ARG_ROUTE.replace("{arg}", "0"))
@@ -2274,7 +2325,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         navController.navigate(TEST_CLASS_ROUTE)
@@ -2293,7 +2344,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class)
+            test<TestClass>()
         }
 
         navController.navigate(TEST_CLASS_ROUTE)
@@ -2312,7 +2363,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
         }
 
         // second nav
@@ -2339,7 +2390,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class) {
+            test<TestClass> {
                 deepLink(
                     navDeepLink<TestClass> { uriPattern = baseUri }
                 )
@@ -2365,7 +2416,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class) {
+            test<TestClass> {
                 deepLink(
                     navDeepLink<TestClass> { uriPattern = baseUri }
                 )
@@ -2391,7 +2442,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class) {
+            test<TestClass> {
                 deepLink(
                     navDeepLink<TestClass> { uriPattern = baseUri }
                 )
@@ -2417,7 +2468,7 @@
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = "start") {
             test("start")
-            test(TestClass::class) {
+            test<TestClass> {
                 deepLink(
                     navDeepLink<TestClass> { uriPattern = baseUri }
                 )
@@ -3188,7 +3239,7 @@
     fun testNavigateWithPopKClassInclusive() {
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = TestClass::class) {
-            test(TestClass::class)
+            test<TestClass>()
             test("second")
         }
 
@@ -3212,7 +3263,7 @@
     fun testNavigateWithPopKClassNotInclusive() {
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = TestClass::class) {
-            test(TestClass::class)
+            test<TestClass>()
             test("second")
             test("third")
         }
@@ -3241,7 +3292,7 @@
     fun testNavigateWithPopObjectInclusive() {
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = TestClass::class) {
-            test(TestClass::class)
+            test<TestClass>()
             test("second")
         }
 
@@ -3265,7 +3316,7 @@
     fun testNavigateWithPopObjectNotInclusive() {
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = TestClass::class) {
-            test(TestClass::class)
+            test<TestClass>()
             test("second")
             test("third")
         }
@@ -3294,7 +3345,7 @@
     fun testNavigateWithPopObjectArg() {
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = TestClassPathArg(0)) {
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
             test("second")
         }
 
@@ -3318,7 +3369,7 @@
     fun testNavigateWithPopObjectWrongArg() {
         val navController = createNavController()
         navController.graph = navController.createGraph(startDestination = TestClassPathArg(0)) {
-            test(TestClassPathArg::class)
+            test<TestClassPathArg>()
             test("second")
         }
 
diff --git a/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
index d321417..b24ff3b 100644
--- a/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
+++ b/testutils/testutils-navigation/src/main/java/androidx/testutils/TestNavigatorDestinationBuilder.kt
@@ -23,8 +23,10 @@
 import androidx.navigation.NavDestinationBuilder
 import androidx.navigation.NavDestinationDsl
 import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavType
 import androidx.navigation.get
 import kotlin.reflect.KClass
+import kotlin.reflect.KType
 
 /**
  * Construct a new [TestNavigator.Destination]
@@ -39,7 +41,9 @@
 /**
  * Construct a new [TestNavigator.Destination]
  */
-inline fun NavGraphBuilder.test(route: KClass<*>) = test(route) {}
+inline fun <reified T : Any> NavGraphBuilder.test(
+    typeMap: Map<KType, NavType<*>> = emptyMap()
+) = test<T>(typeMap) {}
 
 /**
  * Construct a new [TestNavigator.Destination]
@@ -68,21 +72,26 @@
 /**
  * Construct a new [TestNavigator.Destination]
  */
-inline fun NavGraphBuilder.test(
-    route: KClass<*>,
+inline fun <reified T : Any> NavGraphBuilder.test(
+    typeMap: Map<KType, NavType<*>> = emptyMap(),
     builder: TestNavigatorDestinationBuilder.() -> Unit
 ) = destination(
-    TestNavigatorDestinationBuilder(provider[TestNavigator::class], route).apply(builder)
+    TestNavigatorDestinationBuilder(provider[TestNavigator::class], T::class, typeMap)
+        .apply(builder)
 )
 
 /**
  * DSL for constructing a new [TestNavigator.Destination]
  */
 @NavDestinationDsl
+@OptIn(ExperimentalSafeArgsApi::class)
 class TestNavigatorDestinationBuilder : NavDestinationBuilder<TestNavigator.Destination> {
     @Suppress("DEPRECATION")
     constructor(navigator: TestNavigator, @IdRes id: Int = 0) : super(navigator, id)
     constructor(navigator: TestNavigator, route: String) : super(navigator, route)
-    @OptIn(ExperimentalSafeArgsApi::class)
-    constructor(navigator: TestNavigator, route: KClass<*>) : super(navigator, route, emptyMap())
+    constructor(
+        navigator: TestNavigator,
+        route: KClass<*>,
+        typeMap: Map<KType, NavType<*>>
+    ) : super(navigator, route, typeMap)
 }