diff --git a/strada/build.gradle b/strada/build.gradle index 6ceec8d..bf00ff3 100644 --- a/strada/build.gradle +++ b/strada/build.gradle @@ -72,11 +72,13 @@ dependencies { implementation 'androidx.core:core-ktx:1.9.0' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1' + implementation 'androidx.lifecycle:lifecycle-common:2.6.1' testImplementation 'junit:junit:4.13.2' testImplementation 'androidx.test:core:1.5.0' + testImplementation 'androidx.lifecycle:lifecycle-runtime-testing:2.6.1' testImplementation 'org.robolectric:robolectric:4.9.2' - testImplementation 'org.mockito:mockito-core:4.11.0' + testImplementation 'org.mockito:mockito-core:5.2.0' testImplementation 'com.nhaarman:mockito-kotlin:1.6.0' } diff --git a/strada/src/main/kotlin/dev/hotwire/strada/Bridge.kt b/strada/src/main/kotlin/dev/hotwire/strada/Bridge.kt index a027e39..1142185 100644 --- a/strada/src/main/kotlin/dev/hotwire/strada/Bridge.kt +++ b/strada/src/main/kotlin/dev/hotwire/strada/Bridge.kt @@ -9,12 +9,11 @@ private const val bridgeGlobal = "window.nativeBridge" private const val bridgeJavascriptInterface = "Strada" @Suppress("unused") -class Bridge(val webView: WebView) { +class Bridge(private val webView: WebView) { internal var repository = Repository() + private var componentsAreRegistered: Boolean = false - var delegate: BridgeDelegate? = null - var componentsAreRegistered: Boolean = false - private set + var delegate: BridgeDelegate<*>? = null init { // The JavascriptInterface must be added before the page is loaded @@ -22,37 +21,47 @@ class Bridge(val webView: WebView) { } fun register(component: String) { + logEvent("bridgeWillRegisterComponent", component) val javascript = generateJavaScript("register", component.toJsonElement()) evaluate(javascript) } fun register(components: List) { + logEvent("bridgeWillRegisterComponents", components.joinToString()) val javascript = generateJavaScript("register", components.toJsonElement()) evaluate(javascript) } fun unregister(component: String) { + logEvent("bridgeWillUnregisterComponent", component) val javascript = generateJavaScript("unregister", component.toJsonElement()) evaluate(javascript) } fun send(message: Message) { + logMessage("bridgeWillSendMessage", message) val internalMessage = InternalMessage.fromMessage(message) val javascript = generateJavaScript("send", internalMessage.toJson().toJsonElement()) evaluate(javascript) } fun load() { + logEvent("bridgeWillLoad") evaluate(userScript()) } fun reset() { + logEvent("bridgeDidReset") componentsAreRegistered = false } + fun isReady(): Boolean { + return componentsAreRegistered + } + @JavascriptInterface fun bridgeDidInitialize() { - log("bridge initialized") + logEvent("bridgeDidInitialize") runOnUiThread { delegate?.bridgeDidInitialize() } @@ -60,13 +69,12 @@ class Bridge(val webView: WebView) { @JavascriptInterface fun bridgeDidUpdateSupportedComponents() { - log("bridge components registered") + logEvent("bridgeDidUpdateSupportedComponents") componentsAreRegistered = true } @JavascriptInterface fun bridgeDidReceiveMessage(message: String?) { - log("message received: $message") runOnUiThread { InternalMessage.fromJson(message)?.let { delegate?.bridgeDidReceiveMessage(it.toMessage()) @@ -81,7 +89,7 @@ class Bridge(val webView: WebView) { } internal fun evaluate(javascript: String) { - log("evaluating $javascript") + logEvent("evaluatingJavascript", javascript) webView.evaluateJavascript(javascript) {} } diff --git a/strada/src/main/kotlin/dev/hotwire/strada/BridgeComponent.kt b/strada/src/main/kotlin/dev/hotwire/strada/BridgeComponent.kt index 983cfd2..17b08e8 100644 --- a/strada/src/main/kotlin/dev/hotwire/strada/BridgeComponent.kt +++ b/strada/src/main/kotlin/dev/hotwire/strada/BridgeComponent.kt @@ -1,8 +1,17 @@ package dev.hotwire.strada -abstract class BridgeComponent( +abstract class BridgeComponent( val name: String, - private val delegate: BridgeDelegate + private val delegate: BridgeDelegate ) { abstract fun handle(message: Message) + + fun send(message: Message) { + delegate.bridge?.send(message) ?: run { + logEvent("bridgeMessageFailedToSend", "bridge is not available") + } + } + + open fun onStart() {} + open fun onStop() {} } diff --git a/strada/src/main/kotlin/dev/hotwire/strada/BridgeComponentFactory.kt b/strada/src/main/kotlin/dev/hotwire/strada/BridgeComponentFactory.kt index f130e7c..ba35e71 100644 --- a/strada/src/main/kotlin/dev/hotwire/strada/BridgeComponentFactory.kt +++ b/strada/src/main/kotlin/dev/hotwire/strada/BridgeComponentFactory.kt @@ -1,8 +1,8 @@ package dev.hotwire.strada -class BridgeComponentFactory constructor( +class BridgeComponentFactory> constructor( val name: String, - private val creator: (name: String, delegate: D) -> C + private val creator: (name: String, delegate: BridgeDelegate) -> C ) { - fun create(delegate: D) = creator(name, delegate) + fun create(delegate: BridgeDelegate) = creator(name, delegate) } diff --git a/strada/src/main/kotlin/dev/hotwire/strada/BridgeDelegate.kt b/strada/src/main/kotlin/dev/hotwire/strada/BridgeDelegate.kt index d90d951..8a985c0 100644 --- a/strada/src/main/kotlin/dev/hotwire/strada/BridgeDelegate.kt +++ b/strada/src/main/kotlin/dev/hotwire/strada/BridgeDelegate.kt @@ -1,8 +1,100 @@ package dev.hotwire.strada -abstract class BridgeDelegate( - componentFactories: List> +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner + +@Suppress("unused") +class BridgeDelegate( + val destination: D, + private val componentFactories: List>> ) { - abstract fun bridgeDidInitialize() - abstract fun bridgeDidReceiveMessage(message: Message) + internal var bridge: Bridge? = null + private var destinationIsActive = true + private val components = hashMapOf>() + + val activeComponents: List> + get() = when (destinationIsActive) { + true -> components.map { it.value } + else -> emptyList() + } + + init { + observeLifeCycle() + } + + fun loadBridgeInWebView() { + bridge?.load() + } + + fun resetBridge() { + bridge?.reset() + } + + fun onWebViewAttached(bridge: Bridge?) { + this.bridge = bridge + this.bridge?.delegate = this + + if (shouldReloadBridge()) { + loadBridgeInWebView() + } + } + + fun onWebViewDetached() { + bridge?.delegate = null + bridge = null + } + + internal fun bridgeDidInitialize() { + bridge?.register(componentFactories.map { it.name }) + } + + internal fun bridgeDidReceiveMessage(message: Message): Boolean { + return if (destination.bridgeDestinationLocation() == message.metadata?.url) { + logMessage("bridgeDidReceiveMessage", message) + getOrCreateComponent(message.component)?.handle(message) + true + } else { + logMessage("bridgeDidIgnoreMessage", message) + false + } + } + + private fun shouldReloadBridge(): Boolean { + return destination.bridgeWebViewIsReady() && bridge?.isReady() == false + } + + // Lifecycle events + + private fun observeLifeCycle() { + destination.bridgeDestinationLifecycleOwner().lifecycle.addObserver(object : + DefaultLifecycleObserver { + override fun onStart(owner: LifecycleOwner) { onStart() } + override fun onStop(owner: LifecycleOwner) { onStop() } + }) + } + + private fun onStart() { + destinationIsActive = true + activeComponents.forEach { it.onStart() } + } + + private fun onStop() { + destinationIsActive = false + activeComponents.forEach { it.onStop() } + } + + // Retrieve component(s) by type + + inline fun component(): C? { + return activeComponents.filterIsInstance().firstOrNull() + } + + inline fun forEachComponent(action: (C) -> Unit) { + activeComponents.filterIsInstance().forEach { action(it) } + } + + private fun getOrCreateComponent(name: String): BridgeComponent? { + val factory = componentFactories.firstOrNull { it.name == name } ?: return null + return components.getOrPut(name) { factory.create(this) } + } } diff --git a/strada/src/main/kotlin/dev/hotwire/strada/BridgeDestination.kt b/strada/src/main/kotlin/dev/hotwire/strada/BridgeDestination.kt new file mode 100644 index 0000000..3ea6f53 --- /dev/null +++ b/strada/src/main/kotlin/dev/hotwire/strada/BridgeDestination.kt @@ -0,0 +1,9 @@ +package dev.hotwire.strada + +import androidx.lifecycle.LifecycleOwner + +interface BridgeDestination { + fun bridgeDestinationLocation(): String + fun bridgeDestinationLifecycleOwner(): LifecycleOwner + fun bridgeWebViewIsReady(): Boolean +} diff --git a/strada/src/main/kotlin/dev/hotwire/strada/Helpers.kt b/strada/src/main/kotlin/dev/hotwire/strada/Helpers.kt index 37fb763..df07749 100644 --- a/strada/src/main/kotlin/dev/hotwire/strada/Helpers.kt +++ b/strada/src/main/kotlin/dev/hotwire/strada/Helpers.kt @@ -2,7 +2,6 @@ package dev.hotwire.strada import android.os.Handler import android.os.Looper -import android.util.Log /** * Guarantees main thread execution, posting a Runnable on @@ -15,9 +14,3 @@ internal fun runOnUiThread(func: () -> Unit) { else -> Handler(mainLooper).post { func() } } } - -internal fun log(message: String) { - if (BuildConfig.DEBUG) { - Log.d("Strada", "[bridge] $message") - } -} diff --git a/strada/src/main/kotlin/dev/hotwire/strada/InternalMessage.kt b/strada/src/main/kotlin/dev/hotwire/strada/InternalMessage.kt index eb2af18..ce93c74 100644 --- a/strada/src/main/kotlin/dev/hotwire/strada/InternalMessage.kt +++ b/strada/src/main/kotlin/dev/hotwire/strada/InternalMessage.kt @@ -2,8 +2,6 @@ package dev.hotwire.strada import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement @Serializable @@ -11,12 +9,13 @@ internal data class InternalMessage( @SerialName("id") val id: String, @SerialName("component") val component: String, @SerialName("event") val event: String, - @SerialName("data") val data: JsonElement = Json.parseToJsonElement("{}") + @SerialName("data") val data: JsonElement = "{}".parseToJsonElement() ) { fun toMessage() = Message( id = id, component = component, event = event, + metadata = data.decode()?.let { Metadata(url = it.metadata.url) }, jsonData = data.toJson() ) @@ -25,14 +24,19 @@ internal data class InternalMessage( id = message.id, component = message.component, event = message.event, - data = Json.parseToJsonElement(message.jsonData) + data = message.jsonData.parseToJsonElement() ) - fun fromJson(json: String?) = try { - json?.let { Json.decodeFromString(it) } - } catch (e: Exception) { - log("Invalid message: $json") - null - } + fun fromJson(json: String?) = json?.decode() } } + +@Serializable +internal data class InternalDataMetadata( + @SerialName("metadata") val metadata: InternalMetadata +) + +@Serializable +internal data class InternalMetadata( + @SerialName("url") val url: String +) diff --git a/strada/src/main/kotlin/dev/hotwire/strada/JsonExtensions.kt b/strada/src/main/kotlin/dev/hotwire/strada/JsonExtensions.kt index b702711..930a07f 100644 --- a/strada/src/main/kotlin/dev/hotwire/strada/JsonExtensions.kt +++ b/strada/src/main/kotlin/dev/hotwire/strada/JsonExtensions.kt @@ -1,17 +1,30 @@ package dev.hotwire.strada +import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.encodeToJsonElement -internal inline fun T.toJsonElement() = Json.encodeToJsonElement(this) +internal fun String.parseToJsonElement() = json.parseToJsonElement(this) -internal inline fun T.toJson() = Json.encodeToString(this) +internal inline fun T.toJsonElement() = json.encodeToJsonElement(this) + +internal inline fun T.toJson() = json.encodeToString(this) internal inline fun JsonElement.decode(): T? = try { - Json.decodeFromJsonElement(this) + json.decodeFromJsonElement(this) +} catch (e: Exception) { + StradaLog.e("jsonElementDecodeException: ${e.stackTraceToString()}") + null +} + +internal inline fun String.decode(): T? = try { + json.decodeFromString(this) } catch (e: Exception) { + StradaLog.e("jsonStringDecodeException: ${e.stackTraceToString()}") null } + +private val json = Json { ignoreUnknownKeys = true } diff --git a/strada/src/main/kotlin/dev/hotwire/strada/Message.kt b/strada/src/main/kotlin/dev/hotwire/strada/Message.kt index 3dca3ee..8513d33 100644 --- a/strada/src/main/kotlin/dev/hotwire/strada/Message.kt +++ b/strada/src/main/kotlin/dev/hotwire/strada/Message.kt @@ -17,6 +17,11 @@ data class Message( */ val event: String, + /** + * The metadata associated with the message, which includes its url + */ + val metadata: Metadata?, + /** * Data, represented in a json object string, to send along with the message. * For a "page" component, this might be `{"title": "Page Title"}` @@ -35,6 +40,11 @@ data class Message( id = this.id, component = this.component, event = event, + metadata = this.metadata, jsonData = jsonData ) } + +data class Metadata( + val url: String +) diff --git a/strada/src/main/kotlin/dev/hotwire/strada/StradaLog.kt b/strada/src/main/kotlin/dev/hotwire/strada/StradaLog.kt new file mode 100644 index 0000000..c2779bf --- /dev/null +++ b/strada/src/main/kotlin/dev/hotwire/strada/StradaLog.kt @@ -0,0 +1,32 @@ +package dev.hotwire.strada + +import android.util.Log + +@Suppress("unused") +object StradaLog { + private const val DEFAULT_TAG = "StradaLog" + + /** + * Enable debug logging to see message communication from/to the WebView. + */ + var debugLoggingEnabled = false + + internal fun d(msg: String) = log(Log.DEBUG, DEFAULT_TAG, msg) + + internal fun e(msg: String) = log(Log.ERROR, DEFAULT_TAG, msg) + + private fun log(logLevel: Int, tag: String, msg: String) { + when (logLevel) { + Log.DEBUG -> if (debugLoggingEnabled) Log.d(tag, msg) + Log.ERROR -> Log.e(tag, msg) + } + } +} + +internal fun logMessage(event: String, message: Message) { + logEvent(event, message.toString()) +} + +internal fun logEvent(event: String, details: String = "") { + StradaLog.d("$event ".padEnd(35, '.') + " [$details]") +} diff --git a/strada/src/test/kotlin/dev/hotwire/strada/BridgeComponentFactoryTest.kt b/strada/src/test/kotlin/dev/hotwire/strada/BridgeComponentFactoryTest.kt index 7313716..8ad7adc 100644 --- a/strada/src/test/kotlin/dev/hotwire/strada/BridgeComponentFactoryTest.kt +++ b/strada/src/test/kotlin/dev/hotwire/strada/BridgeComponentFactoryTest.kt @@ -1,5 +1,6 @@ package dev.hotwire.strada +import androidx.lifecycle.testing.TestLifecycleOwner import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test @@ -12,37 +13,41 @@ class BridgeComponentFactoryTest { BridgeComponentFactory("two", ::TwoBridgeComponent) ) - val componentOne = factories[0].create(AppBridgeDelegate(factories)) + val delegate = BridgeDelegate( + destination = AppBridgeDestination(), + componentFactories = factories + ) + + val componentOne = factories[0].create(delegate) assertEquals("one", componentOne.name) assertTrue(componentOne is OneBridgeComponent) - val componentTwo = factories[1].create(AppBridgeDelegate(factories)) + val componentTwo = factories[1].create(delegate) assertEquals("two", componentTwo.name) assertTrue(componentTwo is TwoBridgeComponent) } - private class AppBridgeDelegate( - componentFactories: List>, - ) : BridgeDelegate(componentFactories) { - override fun bridgeDidInitialize() {} - override fun bridgeDidReceiveMessage(message: Message) {} + class AppBridgeDestination : BridgeDestination { + override fun bridgeDestinationLocation() = "https://37signals.com" + override fun bridgeDestinationLifecycleOwner() = TestLifecycleOwner() + override fun bridgeWebViewIsReady() = true } private abstract class AppBridgeComponent( name: String, - delegate: AppBridgeDelegate - ) : BridgeComponent(name, delegate) + delegate: BridgeDelegate + ) : BridgeComponent(name, delegate) private class OneBridgeComponent( name: String, - delegate: AppBridgeDelegate + delegate: BridgeDelegate ) : AppBridgeComponent(name, delegate) { override fun handle(message: Message) {} } private class TwoBridgeComponent( name: String, - delegate: AppBridgeDelegate + delegate: BridgeDelegate ) : AppBridgeComponent(name, delegate) { override fun handle(message: Message) {} } diff --git a/strada/src/test/kotlin/dev/hotwire/strada/BridgeDelegateTest.kt b/strada/src/test/kotlin/dev/hotwire/strada/BridgeDelegateTest.kt new file mode 100644 index 0000000..0eefdd6 --- /dev/null +++ b/strada/src/test/kotlin/dev/hotwire/strada/BridgeDelegateTest.kt @@ -0,0 +1,134 @@ +package dev.hotwire.strada + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.testing.TestLifecycleOwner +import com.nhaarman.mockito_kotlin.* +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.verify + +class BridgeDelegateTest { + private lateinit var delegate: BridgeDelegate + private val bridge: Bridge = mock() + + private val factories = listOf( + BridgeComponentFactory("one", ::OneBridgeComponent), + BridgeComponentFactory("two", ::TwoBridgeComponent) + ) + + @Before + fun setup() { + delegate = BridgeDelegate( + destination = AppBridgeDestination(), + componentFactories = factories + ) + } + + @Test + fun loadBridgeInWebView() { + delegate.onWebViewAttached(bridge) + delegate.loadBridgeInWebView() + verify(bridge, times(2)).load() + } + + @Test + fun resetBridge() { + delegate.onWebViewAttached(bridge) + delegate.resetBridge() + verify(bridge).reset() + } + + @Test + fun bridgeDidInitialize() { + delegate.onWebViewAttached(bridge) + delegate.bridgeDidInitialize() + verify(bridge).register(eq(listOf("one", "two"))) + } + + @Test + fun bridgeDidReceiveMessage() { + val message = Message( + id = "1", + component = "one", + event = "connect", + metadata = Metadata("https://37signals.com"), + jsonData = """{"title":"Page-title","subtitle":"Page-subtitle"}""" + ) + + assertNull(delegate.component()) + assertEquals(true, delegate.bridgeDidReceiveMessage(message)) + assertNotNull(delegate.component()) + } + + @Test + fun bridgeDidReceiveMessageIgnored() { + val message = Message( + id = "1", + component = "page", + event = "connect", + metadata = Metadata("https://37signals.com/another_url"), + jsonData = """{"title":"Page-title","subtitle":"Page-subtitle"}""" + ) + + assertEquals(false, delegate.bridgeDidReceiveMessage(message)) + } + + @Test + fun onWebViewAttached() { + whenever(bridge.isReady()).thenReturn(false) + delegate.onWebViewAttached(bridge) + + assertEquals(delegate.bridge, bridge) + } + + @Test + fun onWebViewAttachedShouldLoad() { + whenever(bridge.isReady()).thenReturn(false) + delegate.onWebViewAttached(bridge) + + verify(bridge).load() + } + + @Test + fun onWebViewAttachedShouldNotLoad() { + whenever(bridge.isReady()).thenReturn(true) + delegate.onWebViewAttached(bridge) + + verify(bridge, never()).load() + } + + @Test + fun onWebViewDetached() { + delegate.onWebViewAttached(bridge) + delegate.onWebViewDetached() + + assertNull(delegate.bridge?.delegate) + assertNull(delegate.bridge) + } + + class AppBridgeDestination : BridgeDestination { + override fun bridgeDestinationLocation() = "https://37signals.com" + override fun bridgeDestinationLifecycleOwner() = TestLifecycleOwner(Lifecycle.State.STARTED) + override fun bridgeWebViewIsReady() = true + } + + private abstract class AppBridgeComponent( + name: String, + delegate: BridgeDelegate + ) : BridgeComponent(name, delegate) + + private class OneBridgeComponent( + name: String, + delegate: BridgeDelegate + ) : AppBridgeComponent(name, delegate) { + override fun handle(message: Message) {} + } + + private class TwoBridgeComponent( + name: String, + delegate: BridgeDelegate + ) : AppBridgeComponent(name, delegate) { + override fun handle(message: Message) {} + } +} diff --git a/strada/src/test/kotlin/dev/hotwire/strada/BridgeTest.kt b/strada/src/test/kotlin/dev/hotwire/strada/BridgeTest.kt index 478d06d..ddd7b82 100644 --- a/strada/src/test/kotlin/dev/hotwire/strada/BridgeTest.kt +++ b/strada/src/test/kotlin/dev/hotwire/strada/BridgeTest.kt @@ -2,6 +2,8 @@ package dev.hotwire.strada import android.content.Context import android.webkit.WebView +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.testing.TestLifecycleOwner import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.mock @@ -16,7 +18,7 @@ class BridgeTest { private val webView: WebView = mock() private val context: Context = mock() private val repository: Repository = mock() - private val delegate: BridgeDelegate = mock() + private val delegate: BridgeDelegate = mock() @Before fun setup() { @@ -54,6 +56,7 @@ class BridgeTest { id = "1", component = "page", event = "connect", + metadata = Metadata("https://37signals.com"), jsonData = data ) @@ -79,12 +82,13 @@ class BridgeTest { @Test fun bridgeDidReceiveMessage() { - val json = """{"id":"1","component":"page","event":"connect","data":{"title":"Page title","subtitle":"Page subtitle"}}""" - val data = """{"title":"Page title","subtitle":"Page subtitle"}""" + val json = """{"id":"1","component":"page","event":"connect","data":{"metadata":{"url":"https://37signals.com"},"title":"Page title","subtitle":"Page subtitle"}}""" + val data = """{"metadata":{"url":"https://37signals.com"},"title":"Page title","subtitle":"Page subtitle"}""" val message = Message( id = "1", component = "page", event = "connect", + metadata = Metadata("https://37signals.com"), jsonData = data ) @@ -131,4 +135,10 @@ class BridgeTest { fun sanitizeFunctionName() { assertEquals(bridge.sanitizeFunctionName("send()"), "send") } + + class AppBridgeDestination : BridgeDestination { + override fun bridgeDestinationLocation() = "https://37signals.com" + override fun bridgeDestinationLifecycleOwner() = TestLifecycleOwner() + override fun bridgeWebViewIsReady() = true + } } diff --git a/strada/src/test/kotlin/dev/hotwire/strada/InternalMessageTest.kt b/strada/src/test/kotlin/dev/hotwire/strada/InternalMessageTest.kt index a300733..c0832a4 100644 --- a/strada/src/test/kotlin/dev/hotwire/strada/InternalMessageTest.kt +++ b/strada/src/test/kotlin/dev/hotwire/strada/InternalMessageTest.kt @@ -9,6 +9,7 @@ import org.junit.Test class InternalMessageTest { @Serializable private data class Page( + @SerialName("metadata") val metadata: InternalMetadata, @SerialName("title") val title: String, @SerialName("subtitle") val subtitle: String, @SerialName("actions") val actions: List @@ -19,6 +20,9 @@ class InternalMessageTest { "component":"page", "event":"connect", "data":{ + "metadata":{ + "url":"https://37signals.com" + }, "title":"Page-title", "subtitle":"Page-subtitle", "actions": [ @@ -31,7 +35,7 @@ class InternalMessageTest { @Test fun toMessage() { - val messageJsonData = """{"title":"Page-title","subtitle":"Page-subtitle","actions":["one","two","three"]}""" + val messageJsonData = """{"metadata":{"url":"https://37signals.com"},"title":"Page-title","subtitle":"Page-subtitle","actions":["one","two","three"]}""" val message = InternalMessage( id = "1", component = "page", @@ -42,6 +46,7 @@ class InternalMessageTest { assertEquals("1", message.id) assertEquals("page", message.component) assertEquals("connect", message.event) + assertEquals("https://37signals.com", message.metadata?.url) assertEquals(messageJsonData, message.jsonData) } @@ -74,7 +79,7 @@ class InternalMessageTest { @Test fun fromJsonNoData() { - val noDataJson = """{"id":"1","component":"page","event":"connect"}""" + val noDataJson = """{"id":"1","component":"page","event":"connect","metadata":{"url":"https://37signals.com"}}""" val message = InternalMessage.fromJson(noDataJson) assertEquals("1", message?.id) @@ -83,6 +88,7 @@ class InternalMessageTest { private fun createPage(): Page { return Page( + metadata = InternalMetadata(url = "https://37signals.com"), title = "Page-title", subtitle = "Page-subtitle", actions = listOf("one", "two", "three") diff --git a/strada/src/test/kotlin/dev/hotwire/strada/MessageTest.kt b/strada/src/test/kotlin/dev/hotwire/strada/MessageTest.kt index dd31cf0..7bd693c 100644 --- a/strada/src/test/kotlin/dev/hotwire/strada/MessageTest.kt +++ b/strada/src/test/kotlin/dev/hotwire/strada/MessageTest.kt @@ -6,10 +6,12 @@ import org.junit.Test class MessageTest { @Test fun replacing() { + val metadata = Metadata("https://37signals.com") val message = Message( id = "1", component = "page", event = "connect", + metadata = metadata, jsonData = """{"title":"Page-title","subtitle":"Page-subtitle"}""" ) @@ -21,6 +23,7 @@ class MessageTest { assertEquals("1", newMessage.id) assertEquals("page", newMessage.component) assertEquals("disconnect", newMessage.event) + assertEquals(metadata, newMessage.metadata) assertEquals("{}", newMessage.jsonData) } }