From e4c12a63e77dc9b858d7876e699189df35d59da0 Mon Sep 17 00:00:00 2001 From: mokruhlicova Date: Fri, 9 Jul 2021 09:58:56 +0200 Subject: [PATCH] feat/Tests for specifying push resources by name --- .../java/com/exponea/ConfigurationParser.kt | 53 +++++++++----- .../com/exponea/ConfigurationParserTest.kt | 70 +++++++++++++++++++ documentation/CONFIGURATION.md | 10 ++- example/src/App.tsx | 4 +- 4 files changed, 116 insertions(+), 21 deletions(-) diff --git a/android/src/main/java/com/exponea/ConfigurationParser.kt b/android/src/main/java/com/exponea/ConfigurationParser.kt index 01ac341..0da2a9a 100644 --- a/android/src/main/java/com/exponea/ConfigurationParser.kt +++ b/android/src/main/java/com/exponea/ConfigurationParser.kt @@ -8,6 +8,7 @@ import com.exponea.sdk.models.EventType import com.exponea.sdk.models.ExponeaConfiguration import com.exponea.sdk.models.ExponeaProject import com.facebook.react.bridge.ReadableMap +import java.lang.NumberFormatException internal class ConfigurationParser(private val readableMap: ReadableMap) { private val configuration = ExponeaConfiguration() @@ -63,7 +64,7 @@ internal class ConfigurationParser(private val readableMap: ReadableMap) { } } - fun parse(context: Context?): ExponeaConfiguration { + fun parse(context: Context? = null): ExponeaConfiguration { val map = readableMap.toHashMapRecursively() requireProjectAndAuthorization(map) map.forEach { entry -> @@ -95,7 +96,10 @@ internal class ConfigurationParser(private val readableMap: ReadableMap) { "sessionTimeout" -> configuration.sessionTimeout = map.getSafely("sessionTimeout", Double::class) "automaticSessionTracking" -> - configuration.automaticSessionTracking = map.getSafely("automaticSessionTracking", Boolean::class) + configuration.automaticSessionTracking = map.getSafely( + "automaticSessionTracking", + Boolean::class + ) "pushTokenTrackingFrequency" -> { try { val stringValue = map.getSafely("pushTokenTrackingFrequency", String::class) @@ -127,11 +131,14 @@ internal class ConfigurationParser(private val readableMap: ReadableMap) { map.getSafely("automaticPushNotifications", Boolean::class) "pushIconResourceName" -> { val resourceName = map.getSafely("pushIconResourceName", String::class) - var id: Int? = context?.resources?.getIdentifier(resourceName, "drawable", context.packageName) + var id: Int? = context?.resources?.getIdentifier( + resourceName, + "drawable", + context.packageName + ) if (id == null || id == 0) { - //try to find resource in mipmap if not present in drawable folder + // try to find resource in mipmap if not present in drawable folder id = context?.resources?.getIdentifier(resourceName, "mipmap", context.packageName) - } if (id != null && id > 0) { configuration.pushIcon = id @@ -142,20 +149,31 @@ internal class ConfigurationParser(private val readableMap: ReadableMap) { "pushAccentColor" -> configuration.pushAccentColor = map.getSafely("pushAccentColor", Double::class).toInt() "pushAccentColorRGBA" -> { - val channels = parseRGBA(map.getSafely("pushAccentColorRGBA", String::class)) - if (channels.size == 4) { - configuration.pushAccentColor = Color.argb(channels[3], channels[0], channels[0], channels[2]) - } else throw ExponeaModule.ExponeaDataException( - "Incorrect value '${entry.value}' for key ${entry.key}." - ) + try { + val channels = parseRGBA(map.getSafely("pushAccentColorRGBA", String::class)) + if (channels.size == 4) { + configuration.pushAccentColor = Color.argb( + channels[3], + channels[0], + channels[1], + channels[2] + ) + } else throw ExponeaModule.ExponeaDataException( + "Incorrect value '${entry.value}' for key ${entry.key}." + ) + } catch (ex: NumberFormatException) { + throw ExponeaModule.ExponeaDataException( + "Incorrect value '${entry.value}' for key ${entry.key}." + ) + } } "pushAccentColorName" -> { - val colorName = map.getSafely("pushAccentColorName", String::class) - val resources = context?.resources - val id: Int? = resources?.getIdentifier(colorName, "color", context.packageName) - if (id != null && id > 0) { - configuration.pushAccentColor = ResourcesCompat.getColor(resources, id, null) - } + val colorName = map.getSafely("pushAccentColorName", String::class) + val resources = context?.resources + val id: Int? = resources?.getIdentifier(colorName, "color", context.packageName) + if (id != null && id > 0) { + configuration.pushAccentColor = ResourcesCompat.getColor(resources, id, null) + } } "pushChannelName" -> configuration.pushChannelName = map.getSafely("pushChannelName", String::class) @@ -195,6 +213,5 @@ internal class ConfigurationParser(private val readableMap: ReadableMap) { val channel = it.trim() channel.toInt() } - } } diff --git a/android/src/test/java/com/exponea/ConfigurationParserTest.kt b/android/src/test/java/com/exponea/ConfigurationParserTest.kt index 9b9824e..960d49d 100644 --- a/android/src/test/java/com/exponea/ConfigurationParserTest.kt +++ b/android/src/test/java/com/exponea/ConfigurationParserTest.kt @@ -1,16 +1,26 @@ package com.exponea import android.app.NotificationManager +import android.graphics.Color +import androidx.test.core.app.ApplicationProvider import com.exponea.sdk.models.EventType import com.exponea.sdk.models.ExponeaConfiguration import com.exponea.sdk.models.ExponeaProject import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.ReadableMap +import io.mockk.every +import io.mockk.mockkStatic import java.io.File import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Assert.fail import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE) internal class ConfigurationParserTest { @Test fun `should parse minimal configuration`() { @@ -82,4 +92,64 @@ internal class ConfigurationParserTest { assertEquals("Incorrect type for key 'projectToken'. Expected String got Double", e.message) } } + + @Test + fun `should figure out color from RGBA channels correctly`() { + val data = JavaOnlyMap.of( + "projectToken", "mock-project-token", + "authorizationToken", "mock-authorization-token", + "baseUrl", "http://mock-base-url.xxx", + "android", JavaOnlyMap.of("pushAccentColorRGBA", "100, 100, 90, 150")) + + mockkStatic("android.graphics.Color") + every { Color.argb(150, 100, 100, 90) } returns 123 + + assertEquals( + ExponeaConfiguration( + projectToken = "mock-project-token", + authorization = "Token mock-authorization-token", + baseURL = "http://mock-base-url.xxx", + pushAccentColor = 123 + ), + ConfigurationParser(data as ReadableMap).parse() + ) + } + + @Test + fun `should provide error on wrong color format`() { + val data = JavaOnlyMap.of( + "projectToken", "mock-project-token", + "authorizationToken", "mock-authorization-token", + "baseUrl", "http://mock-base-url.xxx", + "android", JavaOnlyMap.of("pushAccentColorRGBA", "100, text, 90, 150, &")) + try { + mockkStatic("android.graphics.Color") + ConfigurationParser(data as ReadableMap).parse() + fail("Should throw exception") + } catch (e: Exception) { + assertEquals("Incorrect value '100, text, 90, 150, &' for key pushAccentColorRGBA.", e.message) + } + } + + @Test + fun `should not fail when color not found in resources`() { + val data = JavaOnlyMap.of( + "projectToken", "mock-project-token", + "authorizationToken", "mock-authorization-token", + "baseUrl", "http://mock-base-url.xxx", + "android", JavaOnlyMap.of("pushAccentColorName", "my_color")) + val config = ConfigurationParser(data as ReadableMap).parse(ApplicationProvider.getApplicationContext()) + assertNull(config.pushAccentColor) + } + + @Test + fun `should not fail when icon not found in resources`() { + val data = JavaOnlyMap.of( + "projectToken", "mock-project-token", + "authorizationToken", "mock-authorization-token", + "baseUrl", "http://mock-base-url.xxx", + "android", JavaOnlyMap.of("pushIconResourceName", "my_icon")) + val config = ConfigurationParser(data as ReadableMap).parse(ApplicationProvider.getApplicationContext()) + assertNull(config.pushIcon) + } } diff --git a/documentation/CONFIGURATION.md b/documentation/CONFIGURATION.md index 5210778..725cfcd 100644 --- a/documentation/CONFIGURATION.md +++ b/documentation/CONFIGURATION.md @@ -1,3 +1,4 @@ + # Configuration Before using most of the SDK functionality, you'll need to configure Exponea to connect it to backend application. @@ -74,7 +75,14 @@ You can see the Typescript definition for Configuration object at [src/Configura * **pushIcon** Android resource id of the icon to be used for push notifications. -* **pushAccentColor** Accent color of push notification icon and buttons +* **pushIconResourceName** Android resource name of the icon to be used for push notifications. For example, if file `push_icon.png` is placed in your drawable of mipmap resources folder, use the filename without extension as a value. + +* **pushAccentColor** Accent color of push notification icon and buttons, specified as Color ARGB integer + +* **pushAccentColorRGBA** Accent color of push notification icon and buttons, specified by RGBA channels separated by comma. For example, to use the colour blue, the string `"0, 0, 255, 255"` should be entered. + +* **pushAccentColorName** Accent color of push notification icon and buttons, specified by resource name. Any color defined in R class can be used. For example, if you defined your color as a resource +`#0000ff`, use `push_accent_color` as a value for this parameter. * **pushChannelName?** Channel name for push notifications. Only for API level 26+ diff --git a/example/src/App.tsx b/example/src/App.tsx index 68b79fc..7671388 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -86,8 +86,8 @@ export default class App extends React.Component<{}, AppState> { }, android: { pushIconResourceName: 'push_icon', - pushAccentColorRGBA: '161, 226, 200, 220' - } + pushAccentColorRGBA: '161, 226, 200, 220', + }, }) .then(() => { this.setState({sdkConfigured: true});