Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,10 @@ internal fun ReadableMap.buttonLayoutFromMap(context: Context): ButtonLayout? {

internal fun ReadableMap.buttonSettingsFromMap(context: Context): ButtonSettings {
val assetManager = context.assets
val buttonType = getString("buttonType")?.deserializeButtonType() ?: ButtonType.MORE

return ButtonSettings(
type = getString("buttonType")!!.deserializeButtonType(),
type = buttonType,
isAllCaps = getBooleanOrNull("isAllCaps"),
font = assetManager.createFontFromName(getString("fontName")),
textColor = getString("textColorHex")?.deserializeColor(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ internal fun ReadableArray.deserializeUserDecision(): List<UserDecision> {
for (i in 0 until size()) {
val map = getMap(i)
map?.let {
val serviceId = it.getString("serviceId") ?: return@let
decisionList.add(
UserDecision(
it.getString("serviceId")!!,
serviceId,
it.getBoolean("consent")
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package com.usercentrics.reactnative.extensions

import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.JavaOnlyMap
import org.junit.Test
import org.junit.Assert.*

/**
* Tests for nullability handling in React Native 0.77+
* These tests ensure that null values from ReadableArray/ReadableMap are handled safely
*/
class NullabilityTest {

@Test
fun `deserializeUserDecision handles null map entries`() {
// Given: array with null entries
val array = JavaOnlyArray().apply {
pushMap(JavaOnlyMap.of("serviceId", "service1", "consent", true))
pushNull() // Simulate null entry
pushMap(JavaOnlyMap.of("serviceId", "service2", "consent", false))
}

// When: deserializing
val result = array.deserializeUserDecision()

// Then: null entries are skipped, valid entries are processed
assertEquals(2, result.size)
assertEquals("service1", result[0].serviceId)
assertEquals(true, result[0].consent)
assertEquals("service2", result[1].serviceId)
assertEquals(false, result[1].consent)
}

@Test
fun `deserializePurposeLIDecisionsMap handles null map entries`() {
// Given: array with null entries
val array = JavaOnlyArray().apply {
pushMap(JavaOnlyMap.of("id", 1, "legitimateInterestConsent", true))
pushNull() // Simulate null entry
pushMap(JavaOnlyMap.of("id", 2, "legitimateInterestConsent", false))
}

// When: deserializing
val result = array.deserializePurposeLIDecisionsMap()

// Then: null entries are skipped
assertNotNull(result)
assertEquals(2, result?.size)
assertEquals(true, result?.get(1))
assertEquals(false, result?.get(2))
}

@Test
fun `deserializePurposeLIDecisionsMap returns null for empty array`() {
// Given: empty array
val array = JavaOnlyArray()

// When: deserializing
val result = array.deserializePurposeLIDecisionsMap()

// Then: returns null
assertNull(result)
}

@Test
fun `deserializeTCFUserDecisions handles null purpose entries`() {
// Given: TCF decisions with null entries in purposes array
val purposesArray = JavaOnlyArray().apply {
pushMap(JavaOnlyMap.of("id", 1, "consent", true))
pushNull() // Simulate null entry
pushMap(JavaOnlyMap.of("id", 2, "consent", false))
}

val tcfMap = JavaOnlyMap.of("purposes", purposesArray)

// When: deserializing
val result = tcfMap.deserializeTCFUserDecisions()

// Then: null entries are skipped
assertNotNull(result.purposes)
assertEquals(2, result.purposes?.size)
}

@Test
fun `deserializeTCFUserDecisions handles null special features entries`() {
// Given: TCF decisions with null entries in specialFeatures array
val specialFeaturesArray = JavaOnlyArray().apply {
pushMap(JavaOnlyMap.of("id", 1, "consent", true))
pushNull()
pushMap(JavaOnlyMap.of("id", 2, "consent", false))
}

val tcfMap = JavaOnlyMap.of("specialFeatures", specialFeaturesArray)

// When: deserializing
val result = tcfMap.deserializeTCFUserDecisions()

// Then: null entries are skipped
assertNotNull(result.specialFeatures)
assertEquals(2, result.specialFeatures?.size)
}

@Test
fun `deserializeTCFUserDecisions handles null vendors entries`() {
// Given: TCF decisions with null entries in vendors array
val vendorsArray = JavaOnlyArray().apply {
pushMap(JavaOnlyMap.of("id", 1, "consent", true))
pushNull()
pushMap(JavaOnlyMap.of("id", 2, "consent", false))
}

val tcfMap = JavaOnlyMap.of("vendors", vendorsArray)

// When: deserializing
val result = tcfMap.deserializeTCFUserDecisions()

// Then: null entries are skipped
assertNotNull(result.vendors)
assertEquals(2, result.vendors?.size)
}

@Test
fun `deserializeTCFUserDecisions handles null adTechProviders entries`() {
// Given: TCF decisions with null entries in adTechProviders array
val adTechArray = JavaOnlyArray().apply {
pushMap(JavaOnlyMap.of("id", 1, "consent", true))
pushNull()
pushMap(JavaOnlyMap.of("id", 2, "consent", false))
}

val tcfMap = JavaOnlyMap.of("adTechProviders", adTechArray)

// When: deserializing
val result = tcfMap.deserializeTCFUserDecisions()

// Then: null entries are skipped
assertNotNull(result.adTechProviders)
assertEquals(2, result.adTechProviders.size)
}

@Test
fun `deserializeTCFUserDecisions handles missing arrays gracefully`() {
// Given: empty TCF map
val tcfMap = JavaOnlyMap()

// When: deserializing
val result = tcfMap.deserializeTCFUserDecisions()

// Then: all fields are null or empty
assertNull(result.purposes)
assertNull(result.specialFeatures)
assertNull(result.vendors)
assertEquals(0, result.adTechProviders.size)
}

@Test
fun `deserializeUserDecision handles array with all null entries`() {
// Given: array with only null entries
val array = JavaOnlyArray().apply {
pushNull()
pushNull()
pushNull()
}

// When: deserializing
val result = array.deserializeUserDecision()

// Then: returns empty list
assertEquals(0, result.size)
}

@Test
fun `deserializeUserDecision skips entries with null serviceId`() {
// Given: array with entry missing serviceId
val array = JavaOnlyArray().apply {
pushMap(JavaOnlyMap.of("serviceId", "service1", "consent", true))
pushMap(JavaOnlyMap.of("consent", false)) // Missing serviceId
pushMap(JavaOnlyMap.of("serviceId", "service2", "consent", true))
}

// When: deserializing
val result = array.deserializeUserDecision()

// Then: entry without serviceId is skipped
assertEquals(2, result.size)
assertEquals("service1", result[0].serviceId)
assertEquals("service2", result[1].serviceId)
}
}
Loading