From 1bc799091110d530a7a28d4351d045df0012ae38 Mon Sep 17 00:00:00 2001 From: Donn Felker Date: Sun, 16 Mar 2025 08:10:29 -0400 Subject: [PATCH 1/3] Adding onCompletion callback for when path configuration is loaded --- .../kotlin/dev/hotwire/core/config/Hotwire.kt | 5 +- .../core/turbo/config/PathConfiguration.kt | 4 +- .../turbo/config/PathConfigurationTest.kt | 59 +++++++++++++++++++ .../dev/hotwire/demo/DemoApplication.kt | 6 +- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/dev/hotwire/core/config/Hotwire.kt b/core/src/main/kotlin/dev/hotwire/core/config/Hotwire.kt index 768cd50..48a6e27 100644 --- a/core/src/main/kotlin/dev/hotwire/core/config/Hotwire.kt +++ b/core/src/main/kotlin/dev/hotwire/core/config/Hotwire.kt @@ -26,8 +26,9 @@ object Hotwire { fun loadPathConfiguration( context: Context, location: PathConfiguration.Location, - options: PathConfiguration.LoaderOptions = PathConfiguration.LoaderOptions() + options: PathConfiguration.LoaderOptions = PathConfiguration.LoaderOptions(), + onCompletion: (PathConfiguration) -> Unit = {} ) { - config.pathConfiguration.load(context.applicationContext, location, options) + config.pathConfiguration.load(context.applicationContext, location, options, onCompletion) } } diff --git a/core/src/main/kotlin/dev/hotwire/core/turbo/config/PathConfiguration.kt b/core/src/main/kotlin/dev/hotwire/core/turbo/config/PathConfiguration.kt index bfa5cc7..c0646aa 100644 --- a/core/src/main/kotlin/dev/hotwire/core/turbo/config/PathConfiguration.kt +++ b/core/src/main/kotlin/dev/hotwire/core/turbo/config/PathConfiguration.kt @@ -71,7 +71,8 @@ class PathConfiguration { fun load( context: Context, location: Location, - options: LoaderOptions + options: LoaderOptions, + onCompletion: (PathConfiguration) -> Unit = {} ) { if (loader == null) { loader = PathConfigurationLoader(context.applicationContext) @@ -81,6 +82,7 @@ class PathConfiguration { cachedProperties.clear() rules = it.rules settings = it.settings + onCompletion(it) } } diff --git a/core/src/test/kotlin/dev/hotwire/core/turbo/config/PathConfigurationTest.kt b/core/src/test/kotlin/dev/hotwire/core/turbo/config/PathConfigurationTest.kt index 0b2ae89..eee90a9 100644 --- a/core/src/test/kotlin/dev/hotwire/core/turbo/config/PathConfigurationTest.kt +++ b/core/src/test/kotlin/dev/hotwire/core/turbo/config/PathConfigurationTest.kt @@ -78,6 +78,65 @@ class PathConfigurationTest : BaseRepositoryTest() { } } + @Test + fun callbackIsInvokedWhenConfigurationIsLoaded() { + val localPath = "/not-animated" + val cachedPath = "/from-cache" + val remotePath = "/from-remote" + + var onCompleteInvocations = 0 + val onCompletionCallback: (PathConfiguration) -> Unit = { pathConfiguration -> + onCompleteInvocations++ + + when (onCompleteInvocations) { + // See PathConfiguration#load for implementation order of calls, this verifies the correct path is loaded in the correct order + + // First call = loading from asset path + 1 -> assertThat(pathConfiguration.rules.filter { it.matches(localPath)}.flatMap { it.patterns }.contains(localPath)).isTrue() + // Second call = from cache + 2 -> assertThat(pathConfiguration.rules.filter { it.matches(cachedPath)}.flatMap { it.patterns }.contains(cachedPath)).isTrue() + // Third call = from remote + 3 -> assertThat(pathConfiguration.rules.filter { it.matches(remotePath)}.flatMap { it.patterns }.contains(remotePath)).isTrue() + } + + } + + val assetFilePath = "json/test-configuration.json" + val remoteUrl = "$url/demo/configurations/android-v1.json" + val location = Location( + assetFilePath = assetFilePath, + remoteFileUrl = remoteUrl + ) + val options = LoaderOptions() + + val assetJson = context.assets.open(assetFilePath).use { String(it.readBytes()) } + // Update a value in the JSON to simulate a different configuration + val cachedFakeJson = assetJson.replace(localPath, cachedPath) + val remoteFakeJson = assetJson.replace(localPath, remotePath) + + // Tell the mock repository to return values for the asset, cached, and remote configurations, changing a field in each to allow for assertion + whenever(mockRepository.getBundledConfiguration(context, assetFilePath)).thenReturn(assetJson) + whenever(mockRepository.getCachedConfigurationForUrl(context, remoteUrl)).thenReturn(cachedFakeJson) + whenever(runBlocking { mockRepository.getRemoteConfiguration(remoteUrl, options)}).thenReturn(remoteFakeJson) + + pathConfiguration = PathConfiguration() + pathConfiguration.loader = PathConfigurationLoader(context).apply { + repository = mockRepository + } + + runBlocking { + pathConfiguration.load(context, location, options, onCompletionCallback) + verify(mockRepository).getCachedConfigurationForUrl(context, remoteUrl) + verify(mockRepository).getRemoteConfiguration(remoteUrl, options) + + // The onComplete callback should be invoked 3 times. + // 1 - asset path + // 2 - from cache + // 3 - from remote + assertThat(onCompleteInvocations).isEqualTo(3) + } + } + @Test fun validConfigurationIsCached() { pathConfiguration.loader = PathConfigurationLoader(context).apply { diff --git a/demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt b/demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt index fee7a03..31f6987 100644 --- a/demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt +++ b/demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt @@ -1,6 +1,7 @@ package dev.hotwire.demo import android.app.Application +import android.util.Log import dev.hotwire.core.BuildConfig import dev.hotwire.core.bridge.BridgeComponentFactory import dev.hotwire.core.bridge.KotlinXJsonConverter @@ -35,7 +36,10 @@ class DemoApplication : Application() { context = this, location = PathConfiguration.Location( assetFilePath = "json/configuration.json" - ) + ), + onCompletion = { + Log.i("DemoApplication", "Path configuration loaded: $it") + } ) // Set the default fragment destination From 4cc20a61aca29250fead275f1c690e84b1be4084 Mon Sep 17 00:00:00 2001 From: Donn Felker Date: Sun, 16 Mar 2025 08:19:39 -0400 Subject: [PATCH 2/3] Adding bundled repository verification --- .../dev/hotwire/core/turbo/config/PathConfigurationTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/test/kotlin/dev/hotwire/core/turbo/config/PathConfigurationTest.kt b/core/src/test/kotlin/dev/hotwire/core/turbo/config/PathConfigurationTest.kt index eee90a9..9cf17bc 100644 --- a/core/src/test/kotlin/dev/hotwire/core/turbo/config/PathConfigurationTest.kt +++ b/core/src/test/kotlin/dev/hotwire/core/turbo/config/PathConfigurationTest.kt @@ -126,6 +126,7 @@ class PathConfigurationTest : BaseRepositoryTest() { runBlocking { pathConfiguration.load(context, location, options, onCompletionCallback) + verify(mockRepository).getBundledConfiguration(context, assetFilePath) verify(mockRepository).getCachedConfigurationForUrl(context, remoteUrl) verify(mockRepository).getRemoteConfiguration(remoteUrl, options) From 0b974127bcc936abeb49676f1d263bc9899fb748 Mon Sep 17 00:00:00 2001 From: Donn Felker Date: Sun, 16 Mar 2025 08:21:01 -0400 Subject: [PATCH 3/3] Clean up log --- demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt b/demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt index 31f6987..f6a2d7a 100644 --- a/demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt +++ b/demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt @@ -38,7 +38,7 @@ class DemoApplication : Application() { assetFilePath = "json/configuration.json" ), onCompletion = { - Log.i("DemoApplication", "Path configuration loaded: $it") + Log.i("DemoApplication", "Path configuration loaded") } )