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..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 @@ -78,6 +78,66 @@ 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).getBundledConfiguration(context, assetFilePath) + 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..f6a2d7a 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") + } ) // Set the default fragment destination