From 2c8c0040bba0944d376bc877d41370a1c5fa7ec2 Mon Sep 17 00:00:00 2001 From: Arthur Bleil Date: Thu, 25 Aug 2022 18:07:31 -0300 Subject: [PATCH 1/4] fix compose performance Signed-off-by: Arthur Bleil --- .../zup/com/nimbus/compose/ComponentData.kt | 61 +++++++++- .../ui/internal/NimbusDisposableEffect.kt | 2 +- .../nimbus/processor/ServerDrivenProcessor.kt | 114 +++++++++--------- 3 files changed, 117 insertions(+), 60 deletions(-) diff --git a/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt b/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt index 3be3b92..3f0d6a2 100644 --- a/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt +++ b/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt @@ -2,9 +2,65 @@ package br.zup.com.nimbus.compose import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable import com.zup.nimbus.core.tree.ServerDrivenNode +import java.util.Arrays +import kotlin.system.measureTimeMillis + +private fun componentListsAreEqual(list: List<*>, comparable: List<*>): Boolean { + return list.size == comparable.size && list.toSet() == comparable.toSet() +} + +private fun componentMapsAreEqual(map: Map, comparable: Map): Boolean { + val requiresDeepComparison = map.size == comparable.size && map.keys == comparable.keys + if (requiresDeepComparison) { + var areEqual = true + for (entry in map.iterator()) { + areEqual = when (entry.value) { + is Function<*> -> continue + is Map<*, *> -> componentMapsAreEqual( + entry.value as Map, + comparable[entry.key] as Map + ) + is Array<*> -> (entry.value as Array<*>).contentEquals(comparable[entry.key] as Array<*>) + is List<*> -> componentListsAreEqual(entry.value as List<*>, comparable as List<*>) + else -> entry.value == comparable[entry.key] + } + if (!areEqual) break + } + return areEqual + } + return false +} + +private fun componentsAreEquals(node: ServerDrivenNode, comparable: ServerDrivenNode): Boolean { + return !( + node.id != comparable.id || + node.component != comparable.component || + !( + (node.properties == comparable.properties) || ( + node.properties?.let { otherProperties -> + comparable.properties?.let { currentProperties -> + componentMapsAreEqual(otherProperties, currentProperties) + } + } == true + ) + ) || + !( + (node.children == comparable.children) || ( + node.children?.let { otherChildren -> + comparable.children?.let { currentChildren -> + (otherChildren.map { it.id }).toTypedArray() + .contentEquals((currentChildren.map { it.id }).toTypedArray()) + } + } == true + ) + ) + ) +} @Immutable +@Stable class ComponentData( val node: ServerDrivenNode, val parent: ServerDrivenNode?, @@ -17,6 +73,9 @@ class ComponentData( } override fun equals(other: Any?): Boolean { - return if (other is ComponentData) other.hashCode() == hash else false + return when (other) { + is ComponentData -> componentsAreEquals(other.node, node) + else -> false + } } } diff --git a/compose/src/main/java/br/zup/com/nimbus/compose/core/ui/internal/NimbusDisposableEffect.kt b/compose/src/main/java/br/zup/com/nimbus/compose/core/ui/internal/NimbusDisposableEffect.kt index 383f212..13a0d4a 100644 --- a/compose/src/main/java/br/zup/com/nimbus/compose/core/ui/internal/NimbusDisposableEffect.kt +++ b/compose/src/main/java/br/zup/com/nimbus/compose/core/ui/internal/NimbusDisposableEffect.kt @@ -21,8 +21,8 @@ internal fun NimbusDisposableEffect( Lifecycle.Event.ON_START -> onStart() Lifecycle.Event.ON_CREATE -> onCreate() Lifecycle.Event.ON_DESTROY -> onDestroy() + else -> {} } - } // Add the observer to the lifecycle diff --git a/processor/src/main/java/com/zup/nimbus/processor/ServerDrivenProcessor.kt b/processor/src/main/java/com/zup/nimbus/processor/ServerDrivenProcessor.kt index d732912..832f857 100644 --- a/processor/src/main/java/com/zup/nimbus/processor/ServerDrivenProcessor.kt +++ b/processor/src/main/java/com/zup/nimbus/processor/ServerDrivenProcessor.kt @@ -24,11 +24,9 @@ import com.squareup.kotlinpoet.asTypeName import kotlin.reflect.KClass class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment): SymbolProcessor { - private fun Resolver.findAnnotations( - kClass: KClass<*>, - ) = getSymbolsWithAnnotation( - kClass.qualifiedName.toString()) - .filterIsInstance() + private fun Resolver.findAnnotations(kClass: KClass<*>) = + getSymbolsWithAnnotation(kClass.qualifiedName.toString()) + .filterIsInstance() private fun createNimbusComposable( builder: FileSpec.Builder, @@ -40,8 +38,7 @@ class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment) .addAnnotation(ClassNames.Composable) .addParameter("data", ClassNames.ComponentData) .addStatement("var nimbus = NimbusTheme.nimbus") - .addStatement("val properties = remember { " + - "ComponentDeserializer(logger = nimbus.logger, node = data.node) }") + .addStatement("val properties = remember { ComponentDeserializer(logger = nimbus.logger, node = data.node) }") .addStatement("properties.start()") component.parameters.forEach { @@ -49,7 +46,7 @@ class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment) if (!it.nullable) throw RequiredParentException(it.name, fn) fnBuilder.addStatement("val %L = data.parent?.component", it.name) } else if (it.deserializer != null) { - if(it.deserializer.packageName != fn.packageName.asString()) { + if (it.deserializer.packageName != fn.packageName.asString()) { builder.addClassImport(it.deserializer) } fnBuilder.addStatement( @@ -80,17 +77,17 @@ class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment) TypeCategory.ServerDrivenAction -> { if (it.arity == 0) { fnBuilder.addStatement( - "val %LAction = properties.asAction%L(%S)", + "val %LAction = remember(properties) { properties.asAction%L(%S) }", it.name, if (it.nullable) "OrNull" else "", it.name, ) - val template = if (it.nullable) "val %L = %LAction?.let{ { it(null) } }" - else "val %L = { %LAction(null) }" + val template = if (it.nullable) "val %L = %LAction?.let{ remember(properties) { { it(null) } } }" + else "val %L = remember(properties) { { %LAction(null) } }" fnBuilder.addStatement(template, it.name, it.name) } else { fnBuilder.addStatement( - "val %L = properties.asAction%L(%S)", + "val %L = remember(properties) { properties.asAction%L(%S) }", it.name, if (it.nullable) "OrNull" else "", it.name, @@ -139,11 +136,8 @@ class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment) ): Set { val mustDeserialize = mutableSetOf() val sourceFiles = functions.mapNotNull { it.containingFile } - - val componentsFile = FileSpec.builder( - packageName, - "generatedComponents", - ).addClassImport(ClassNames.NimbusTheme) + val componentsFile = FileSpec.builder(packageName,"generatedComponents") + .addClassImport(ClassNames.NimbusTheme) .addClassImport(ClassNames.ComponentDeserializer) .addClassImport(ClassNames.NimbusMode) .addClassImport(ClassNames.Text) @@ -157,10 +151,7 @@ class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment) if (mustDeserialize.isNotEmpty()) componentsFile.addClassImport(entityDeserializerRef) val file = environment.codeGenerator.createNewFile( - Dependencies( - false, - *sourceFiles.toList().toTypedArray(), - ), + Dependencies(false, *sourceFiles.toList().toTypedArray()), packageName, "generatedComponents" ) @@ -170,7 +161,8 @@ class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment) } fun createClassDeserializer(builder: FileSpec.Builder, clazz: KSClassDeclaration): FunSpec { - val name = "${clazz.packageName.asString()}.${clazz.simpleName.asString()}".replace(".", "_") + val name = "${clazz.packageName.asString()}.${clazz.simpleName.asString()}" + .replace(".", "_") val fnBuilder = FunSpec.builder(name) .addParameter("properties", ClassNames.ComponentDeserializer) .addModifiers(KModifier.PRIVATE) @@ -180,7 +172,7 @@ class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment) ) constructorInfo.parameters.forEach { if (it.deserializer != null) { - if(it.deserializer.packageName != clazz.packageName.asString()) { + if (it.deserializer.packageName != clazz.packageName.asString()) { builder.addClassImport(it.deserializer) } fnBuilder.addStatement( @@ -259,45 +251,51 @@ class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment) ) val objectBuilder = TypeSpec.objectBuilder("NimbusEntityDeserializer") - .addProperty(PropertySpec.builder( - "deserializers", - ClassName("kotlin.collections", "MutableMap") - .parameterizedBy( - String::class.asTypeName(), - LambdaTypeName.get( - parameters = listOf(ParameterSpec.builder( - "properties", - ClassNames.ComponentDeserializer, - ).build()), - returnType = Any::class.asTypeName(), - ) - ), - KModifier.PRIVATE, - ).initializer( - CodeBlock.of( - "mutableMapOf(%L)", - mustDeserialize.joinToString(", ") { - val name = "${it.packageName.asString()}.${it.simpleName.asString()}" - "\"$name\" to { ${name.replace(".", "_")}(it) }" - }, + .addProperty( + PropertySpec.builder( + "deserializers", + ClassName("kotlin.collections", "MutableMap") + .parameterizedBy( + String::class.asTypeName(), + LambdaTypeName.get( + parameters = listOf( + ParameterSpec.builder( + "properties", + ClassNames.ComponentDeserializer, + ).build() + ), + returnType = Any::class.asTypeName(), + ) + ), + KModifier.PRIVATE, + ) + .initializer( + CodeBlock.of( + "mutableMapOf(%L)", + mustDeserialize.joinToString(", ") { + val name = "${it.packageName.asString()}.${it.simpleName.asString()}" + "\"$name\" to { ${name.replace(".", "_")}(it) }" + }, + ) ) - ).build()) + .build() + ) .addFunction( FunSpec.builder("deserialize") - .addTypeVariables(listOf( - TypeVariableName("T"), - TypeVariableName( - "U", - listOf(KClass::class.asClassName().parameterizedBy( - TypeVariableName("T") - )) + .addTypeVariables( + listOf( + TypeVariableName("T"), + TypeVariableName( + "U", + listOf( + KClass::class.asClassName() + .parameterizedBy(TypeVariableName("T")) + ) + ) ) - )) - .addParameter("properties", ClassNames.ComponentDeserializer) - .addParameter( - "clazz", - TypeVariableName("U"), ) + .addParameter("properties", ClassNames.ComponentDeserializer) + .addParameter("clazz", TypeVariableName("U")) .returns(TypeVariableName("T")) .addStatement( "return deserializers.get(clazz.qualifiedName ?: \"\")?.let " + @@ -327,7 +325,7 @@ class ServerDrivenProcessor(private val environment: SymbolProcessorEnvironment) override fun process(resolver: Resolver): List { val functions: Sequence = resolver.findAnnotations(ServerDrivenComponent::class) - if(!functions.iterator().hasNext()) return emptyList() + if (!functions.iterator().hasNext()) return emptyList() val mustDeserialize = mutableSetOf() val byPackage = functions.groupBy { it.packageName.asString() } From e0aaf901d17d721f45f1dc2fd7a9c3f62dab5d22 Mon Sep 17 00:00:00 2001 From: Arthur Bleil Date: Mon, 7 Nov 2022 16:38:00 -0300 Subject: [PATCH 2/4] feat: Navigation Params Signed-off-by: Arthur Bleil --- compose/build.gradle.kts | 2 +- .../nimbus/compose/internal/NimbusNavHost.kt | 34 ++++----- .../internal/NimbusNavigationEffect.kt | 6 +- .../compose/internal/NimbusViewModel.kt | 27 ++++--- .../nimbus/compose/internal/PagesManager.kt | 10 +-- .../br/zup/com/nimbus/compose/model/Page.kt | 4 +- .../com/nimbus/compose/internal/BaseTest.kt | 4 +- .../compose/internal/NimbusViewModelTest.kt | 74 ++++++++++++------- 8 files changed, 87 insertions(+), 74 deletions(-) diff --git a/compose/build.gradle.kts b/compose/build.gradle.kts index ac9237b..0eb7f27 100644 --- a/compose/build.gradle.kts +++ b/compose/build.gradle.kts @@ -11,7 +11,7 @@ val serializationVersion = "1.3.2" val ktorVersion = "1.6.8" dependencies { - api("br.com.zup.nimbus:nimbus-core-android:1.0.0-alpha7") + api("br.com.zup.nimbus:nimbus-core-android:1.0.0-alpha") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion") implementation("io.ktor:ktor-client-core:$ktorVersion") diff --git a/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusNavHost.kt b/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusNavHost.kt index 0d3efbf..821695a 100644 --- a/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusNavHost.kt +++ b/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusNavHost.kt @@ -28,21 +28,19 @@ internal fun NimbusNavHost( nimbusViewModel: NimbusViewModel = viewModel( //Creates a new viewmodel for each unique key key = viewModelKey, - factory = NimbusViewModel.provideFactory( - nimbusConfig = nimbusConfig - ) + factory = NimbusViewModel.provideFactory(nimbusConfig = nimbusConfig) ), modalParentHelper: ModalTransitionDialogHelper = ModalTransitionDialogHelper(), nimbusNavHostHelper: NimbusNavHostHelper = NimbusNavHostHelperImpl(), json: String = "", ) { - NimbusNavigationEffect(nimbusViewModel, navController) NimbusDisposableEffect( onCreate = { initNavHost(nimbusViewModel, viewRequest, json) - }) + } + ) ConfigureNavHostHelper(nimbusNavHostHelper, nimbusViewModel) @@ -54,17 +52,17 @@ internal fun NimbusNavHost( ) { composable( route = SHOW_VIEW_DESTINATION, - arguments = listOf(navArgument(VIEW_URL) { - type = NavType.StringType - defaultValue = VIEW_INITIAL_URL - }) + arguments = listOf( + navArgument(VIEW_URL) { + type = NavType.StringType + defaultValue = VIEW_INITIAL_URL + } + ) ) { backStackEntry -> val arguments = requireNotNull(backStackEntry.arguments) val currentPageUrl = arguments.getString(VIEW_URL) val currentPage = currentPageUrl?.let { - nimbusViewModel.getPageBy( - it - ) + nimbusViewModel.getPageBy(it) } currentPage?.let { page -> NimbusBackHandler() @@ -86,7 +84,6 @@ private fun ConfigureNavHostHelper( ) { nimbusNavHostHelper.nimbusNavHostExecutor = object : NimbusNavHostHelper.NimbusNavHostExecutor { override fun isFirstScreen(): Boolean = nimbusViewModel.getPageCount() == 1 - override fun pop(): Boolean = nimbusViewModel.pop() } } @@ -96,16 +93,15 @@ private fun initNavHost( viewRequest: ViewRequest?, json: String ) { - - if (viewRequest != null) + if (viewRequest != null) { nimbusViewModel.initFirstViewWithRequest(viewRequest = viewRequest) - else + } + else { nimbusViewModel.initFirstViewWithJson(json = json) - + } } interface NimbusNavHostHelper { - var nimbusNavHostExecutor: NimbusNavHostExecutor? interface NimbusNavHostExecutor { @@ -121,9 +117,7 @@ interface NimbusNavHostHelper { * This helper can be used to control some behaviour from outside the NimbusNavHost composable */ internal class NimbusNavHostHelperImpl : NimbusNavHostHelper { - override var nimbusNavHostExecutor: NimbusNavHostHelper.NimbusNavHostExecutor? = null override fun isFirstScreen(): Boolean = nimbusNavHostExecutor?.isFirstScreen() ?: false - override fun pop(): Boolean = nimbusNavHostExecutor?.pop() ?: false } diff --git a/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusNavigationEffect.kt b/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusNavigationEffect.kt index 7a522d4..ee61fe3 100644 --- a/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusNavigationEffect.kt +++ b/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusNavigationEffect.kt @@ -19,11 +19,7 @@ internal fun NimbusNavigationEffect( navController.nimbusPopTo(navigationState.url) } is NimbusViewModelNavigationState.Push -> { - navController.navigate( - "$SHOW_VIEW?$VIEW_URL=${ - navigationState.url - }" - ) + navController.navigate("$SHOW_VIEW?$VIEW_URL=${navigationState.url}") } else -> { } diff --git a/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusViewModel.kt b/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusViewModel.kt index 0110d7c..b45d1b9 100644 --- a/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusViewModel.kt +++ b/compose/src/main/java/br/zup/com/nimbus/compose/internal/NimbusViewModel.kt @@ -8,6 +8,7 @@ import br.zup.com.nimbus.compose.VIEW_INITIAL_URL import br.zup.com.nimbus.compose.VIEW_JSON_DESCRIPTION import br.zup.com.nimbus.compose.model.Page import com.zup.nimbus.core.ServerDrivenNavigator +import com.zup.nimbus.core.ServerDrivenState import com.zup.nimbus.core.ServerDrivenView import com.zup.nimbus.core.network.ViewRequest import kotlinx.coroutines.flow.MutableSharedFlow @@ -32,7 +33,6 @@ internal class NimbusViewModel( private val nimbusConfig: br.zup.com.nimbus.compose.Nimbus, private val pagesManager: PagesManager = PagesManager(), ) : ViewModel() { - private var _nimbusViewModelModalState: MutableSharedFlow = MutableSharedFlow(replay = CoroutineDispatcherLib.REPLAY_COUNT, onBufferOverflow = CoroutineDispatcherLib.ON_BUFFER_OVERFLOW) @@ -40,7 +40,6 @@ internal class NimbusViewModel( val nimbusViewModelModalState: SharedFlow get() = _nimbusViewModelModalState - private var _nimbusViewNavigationState: MutableSharedFlow = MutableSharedFlow(replay = CoroutineDispatcherLib.REPLAY_COUNT, onBufferOverflow = CoroutineDispatcherLib.ON_BUFFER_OVERFLOW) @@ -54,9 +53,7 @@ internal class NimbusViewModel( ): ViewModelProvider.Factory = object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { - return NimbusViewModel( - nimbusConfig = nimbusConfig - ) as T + return NimbusViewModel(nimbusConfig = nimbusConfig) as T } } } @@ -145,28 +142,34 @@ internal class NimbusViewModel( private fun popNavigationTo(url: String) = viewModelScope.launch(CoroutineDispatcherLib.backgroundPool) { - val page = pagesManager.getPageBy(url) - - page?.let { - pagesManager.removePagesAfter(page) + pagesManager.getPageBy(url)?.let { + pagesManager.removePagesAfter(it) setNavigationState(NimbusViewModelNavigationState.PopTo(url)) } } private fun doPushWithViewRequest(request: ViewRequest, initialRequest: Boolean = false) = viewModelScope.launch(CoroutineDispatcherLib.inputOutputPool) { + var statesInstances: List? = null + if (!request.params.isNullOrEmpty()) { + statesInstances = request.params!!.entries.map { + ServerDrivenState(it.key, it.value) + } + } + val view = ServerDrivenView( nimbus = nimbusConfig, getNavigator = { serverDrivenNavigator }, - description = request.url + description = request.url, + states = statesInstances, ) val url = if (initialRequest) VIEW_INITIAL_URL else request.url val page = Page( coroutineScope = viewModelScope, id = url, - view = view) + view = view + ) pushNavigation(page = page, initialRequest = initialRequest) - loadViewRequest(request, view, page) } diff --git a/compose/src/main/java/br/zup/com/nimbus/compose/internal/PagesManager.kt b/compose/src/main/java/br/zup/com/nimbus/compose/internal/PagesManager.kt index ccd8dc6..b3c832f 100644 --- a/compose/src/main/java/br/zup/com/nimbus/compose/internal/PagesManager.kt +++ b/compose/src/main/java/br/zup/com/nimbus/compose/internal/PagesManager.kt @@ -16,7 +16,6 @@ internal class PagesManager { return false } this.removeLastPage() - return true } @@ -24,14 +23,11 @@ internal class PagesManager { pages.clear() } - private fun removeLastPage() = - CoroutineScope(CoroutineDispatcherLib.backgroundPool).launch { - pages.removeLast() - } + private fun removeLastPage() = CoroutineScope(CoroutineDispatcherLib.backgroundPool).launch { + pages.removeLast() + } fun getPageCount() = pages.size fun getPageBy(url: String): Page? = pages.firstOrNull { it.id == url } - fun removePagesAfter(page: Page) = page.removePagesAfter(pages) - } diff --git a/compose/src/main/java/br/zup/com/nimbus/compose/model/Page.kt b/compose/src/main/java/br/zup/com/nimbus/compose/model/Page.kt index b8e17d5..a3edf24 100644 --- a/compose/src/main/java/br/zup/com/nimbus/compose/model/Page.kt +++ b/compose/src/main/java/br/zup/com/nimbus/compose/model/Page.kt @@ -41,7 +41,6 @@ data class Page( fun setContent(tree: RootNode) { change(NimbusPageState.PageStateOnShowPage(tree)) - } fun setLoading() { @@ -73,6 +72,7 @@ data class Page( internal fun Page.removePagesAfter(pages: MutableList) { val index = pages.indexOf(this) - if (index < pages.lastIndex) + if (index < pages.lastIndex) { pages.subList(index + 1, pages.size).clear() + } } diff --git a/compose/src/test/java/br/zup/com/nimbus/compose/internal/BaseTest.kt b/compose/src/test/java/br/zup/com/nimbus/compose/internal/BaseTest.kt index 5b98135..4e41ab5 100644 --- a/compose/src/test/java/br/zup/com/nimbus/compose/internal/BaseTest.kt +++ b/compose/src/test/java/br/zup/com/nimbus/compose/internal/BaseTest.kt @@ -16,7 +16,7 @@ const val BASE_URL = "http://localhost" abstract class BaseTest { internal val nimbusConfig: Nimbus = mockk() internal val pagesManager: PagesManager = mockk() - internal val viewClient: ViewClient = mockk() + private val viewClient: ViewClient = mockk() @BeforeAll open fun setUp() { @@ -28,7 +28,7 @@ abstract class BaseTest { unmockkAll() } - protected fun mockNimbusConfig(){ + private fun mockNimbusConfig(){ every { nimbusConfig.baseUrl } returns BASE_URL every { nimbusConfig.viewClient } returns viewClient } diff --git a/compose/src/test/java/br/zup/com/nimbus/compose/internal/NimbusViewModelTest.kt b/compose/src/test/java/br/zup/com/nimbus/compose/internal/NimbusViewModelTest.kt index a259a76..8ea7a41 100644 --- a/compose/src/test/java/br/zup/com/nimbus/compose/internal/NimbusViewModelTest.kt +++ b/compose/src/test/java/br/zup/com/nimbus/compose/internal/NimbusViewModelTest.kt @@ -23,7 +23,7 @@ import io.mockk.slot import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertEquals +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested @@ -72,8 +72,8 @@ class NimbusViewModelTest : BaseTest() { //Then val stateHistory = observer.awaitStateChanges(2) - assertEquals(NimbusPageState.PageStateOnLoading, stateHistory[0]) - assertEquals(expectedState, stateHistory[1]) + Assertions.assertEquals(NimbusPageState.PageStateOnLoading, stateHistory[0]) + Assertions.assertEquals(expectedState, stateHistory[1]) verify(exactly = 1) { pagesManager.add(any()) } } @@ -103,6 +103,34 @@ class NimbusViewModelTest : BaseTest() { } } + @DisplayName("When initFirstViewWithRequest with navigation params") + @Nested + inner class ViewWithRequestWithNavigationParams { + @DisplayName("Then should init the view with states") + @Test + fun testGivenAViewRequestWhenInitFirstViewShouldInitWithStates() = runTest { + val paramsViewModel = NimbusViewModel(nimbusConfig = nimbusConfig, pagesManager = PagesManager()) + + // Given + val viewRequest = ViewRequest( + url = RandomData.httpUrl(), + params = mapOf( + "testParamState" to "test state param value" + ) + ) + + //When + paramsViewModel.initFirstViewWithRequest(viewRequest) + + val page = paramsViewModel.getPageBy("root") + Assertions.assertNotNull(page) + Assertions.assertNotNull(page!!.view.states) + Assertions.assertEquals(1, page.view.states!!.size) + Assertions.assertEquals(page.view.states!![0].id, "testParamState") + Assertions.assertEquals(page.view.states!![0].get(), "test state param value") + } + } + private fun initFirstViewWithSuccess(): PageStateObserver { // Given val viewRequest = ViewRequest(url = RandomData.httpUrl()) @@ -135,8 +163,8 @@ class NimbusViewModelTest : BaseTest() { //Then val stateHistory = observer.awaitStateChanges(2) - assertEquals(expectedFirstEmission, stateHistory[0]) - assertEquals(expectedSecondEmission, stateHistory[1]) + Assertions.assertEquals(expectedFirstEmission, stateHistory[0]) + Assertions.assertEquals(expectedSecondEmission, stateHistory[1]) verify(exactly = 1) { pagesManager.add(any()) } } @@ -178,8 +206,7 @@ class NimbusViewModelTest : BaseTest() { //Given val expectedFirstEmission = NimbusPageState.PageStateOnLoading val expectedSecondEmission = NimbusPageState.PageStateOnShowPage(serverDrivenNode) - val expectedNavigationFirstEmission = NimbusViewModelNavigationState.Push( - url) + val expectedNavigationFirstEmission = NimbusViewModelNavigationState.Push(url) val expectedNavigationSecondEmission = NimbusViewModelNavigationState.Pop //When @@ -187,8 +214,8 @@ class NimbusViewModelTest : BaseTest() { //Then val stateHistory = observer.awaitStateChanges(2) - assertEquals(expectedFirstEmission, stateHistory[0]) - assertEquals(expectedSecondEmission, stateHistory[1]) + Assertions.assertEquals(expectedFirstEmission, stateHistory[0]) + Assertions.assertEquals(expectedSecondEmission, stateHistory[1]) verify(exactly = 1) { pagesManager.add(any()) } @@ -197,8 +224,8 @@ class NimbusViewModelTest : BaseTest() { serverDrivenNavigatorSlot.pop() viewModel.nimbusViewNavigationState.test { - assertEquals(expectedNavigationFirstEmission, awaitItem()) - assertEquals(expectedNavigationSecondEmission, awaitItem()) + Assertions.assertEquals(expectedNavigationFirstEmission, awaitItem()) + Assertions.assertEquals(expectedNavigationSecondEmission, awaitItem()) } verify(exactly = 2) { pagesManager.add(any()) } @@ -221,10 +248,9 @@ class NimbusViewModelTest : BaseTest() { //Then viewModel.nimbusViewModelModalState.test { - assertEquals(expectedModalState, awaitItem()) - assertEquals(expectedSecondModalState, awaitItem()) + Assertions.assertEquals(expectedModalState, awaitItem()) + Assertions.assertEquals(expectedSecondModalState, awaitItem()) } - } @DisplayName("Then should post NimbusViewModelNavigationState.PopTo(url)") @@ -243,9 +269,8 @@ class NimbusViewModelTest : BaseTest() { //Then viewModel.nimbusViewNavigationState.test { - assertEquals(expectedState, awaitItem()) + Assertions.assertEquals(expectedState, awaitItem()) } - } } @@ -260,8 +285,8 @@ class NimbusViewModelTest : BaseTest() { //Simulates user clicking retry button on error screen secondItem.retry.invoke() val stateHistory = observer.awaitStateChanges(2) - assertEquals(expectedLoading, stateHistory[0]) - assertEquals(expectedOnShowPage, stateHistory[1]) + Assertions.assertEquals(expectedLoading, stateHistory[0]) + Assertions.assertEquals(expectedOnShowPage, stateHistory[1]) } @DisplayName("When receive a view model method call") @@ -280,7 +305,7 @@ class NimbusViewModelTest : BaseTest() { //Then verify(exactly = 1) { pagesManager.popLastPage() } - assertEquals(expectedPop , pop) + Assertions.assertEquals(expectedPop , pop) } @DisplayName("Then should return the page when getPageBy") @@ -297,7 +322,7 @@ class NimbusViewModelTest : BaseTest() { //Then verify(exactly = 1) { pagesManager.getPageBy(url) } - assertEquals(expectedPage , page) + Assertions.assertEquals(expectedPage , page) } @DisplayName("Then should return the page count when getPageCount") @@ -313,13 +338,12 @@ class NimbusViewModelTest : BaseTest() { //Then verify(exactly = 1) { pagesManager.getPageCount() } - assertEquals(expectedPageCount , result) + Assertions.assertEquals(expectedPageCount , result) } @DisplayName("Then should dispose the pages") @Test fun testGivenDisposeCallShouldDisposePages() = runTest { - //Given every { pagesManager.removeAllPages() } just Runs @@ -357,7 +381,7 @@ class NimbusViewModelTest : BaseTest() { //Then viewModel.nimbusViewModelModalState.test { - assertEquals(expectedModalState, awaitItem()) + Assertions.assertEquals(expectedModalState, awaitItem()) } } } @@ -367,9 +391,9 @@ class NimbusViewModelTest : BaseTest() { expectedPageError: NimbusPageState.PageStateOnError, ): NimbusPageState.PageStateOnError { val stateHistory = observer.awaitStateChanges(2) - assertEquals(NimbusPageState.PageStateOnLoading, stateHistory[0]) + Assertions.assertEquals(NimbusPageState.PageStateOnLoading, stateHistory[0]) val errorState = stateHistory[1] as NimbusPageState.PageStateOnError - assertEquals(expectedPageError.throwable, errorState.throwable) + Assertions.assertEquals(expectedPageError.throwable, errorState.throwable) return errorState } } From 7ca8a9732fb3340a3facd1433ba90308ed191afe Mon Sep 17 00:00:00 2001 From: Arthur Bleil Date: Wed, 16 Nov 2022 17:17:15 -0300 Subject: [PATCH 3/4] fix detekt Signed-off-by: Arthur Bleil --- .../src/main/java/br/zup/com/nimbus/compose/ComponentData.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt b/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt index 67b9d51..c703b13 100644 --- a/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt +++ b/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt @@ -11,6 +11,7 @@ private fun componentMapsAreEqual(map: Map, comparable: Map -> continue @@ -29,6 +30,7 @@ private fun componentMapsAreEqual(map: Map, comparable: Map Date: Thu, 17 Nov 2022 14:49:00 -0300 Subject: [PATCH 4/4] remove graveyard code Signed-off-by: Arthur Bleil --- .../zup/com/nimbus/compose/ComponentData.kt | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt b/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt index c703b13..2e6f91f 100644 --- a/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt +++ b/compose/src/main/java/br/zup/com/nimbus/compose/ComponentData.kt @@ -3,60 +3,6 @@ package br.zup.com.nimbus.compose import androidx.compose.runtime.Composable import com.zup.nimbus.core.tree.ServerDrivenNode -private fun componentListsAreEqual(list: List<*>, comparable: List<*>): Boolean { - return list.size == comparable.size && list.toSet() == comparable.toSet() -} - -private fun componentMapsAreEqual(map: Map, comparable: Map): Boolean { - val requiresDeepComparison = map.size == comparable.size && map.keys == comparable.keys - if (requiresDeepComparison) { - var areEqual = true - @Suppress("LoopWithTooManyJumpStatements") - for (entry in map.iterator()) { - areEqual = when (entry.value) { - is Function<*> -> continue - is Map<*, *> -> componentMapsAreEqual( - entry.value as Map, - comparable[entry.key] as Map - ) - is Array<*> -> (entry.value as Array<*>).contentEquals(comparable[entry.key] as Array<*>) - is List<*> -> componentListsAreEqual(entry.value as List<*>, comparable as List<*>) - else -> entry.value == comparable[entry.key] - } - if (!areEqual) break - } - return areEqual - } - return false -} - -@Suppress("UnusedPrivateMember") -private fun componentsAreEquals(node: ServerDrivenNode, comparable: ServerDrivenNode): Boolean { - return !( - node.id != comparable.id || - node.component != comparable.component || - !( - (node.properties == comparable.properties) || ( - node.properties?.let { otherProperties -> - comparable.properties?.let { currentProperties -> - componentMapsAreEqual(otherProperties, currentProperties) - } - } == true - ) - ) || - !( - (node.children == comparable.children) || ( - node.children?.let { otherChildren -> - comparable.children?.let { currentChildren -> - (otherChildren.map { it.id }).toTypedArray() - .contentEquals((currentChildren.map { it.id }).toTypedArray()) - } - } == true - ) - ) - ) -} - class ComponentData( val node: ServerDrivenNode, val children: @Composable () -> Unit,