Skip to content
Draft
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
@@ -0,0 +1,94 @@
package com.adobe.marketing.mobile.aepcomposeui.components

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.adobe.marketing.mobile.aepcomposeui.AepUI
import com.adobe.marketing.mobile.aepcomposeui.SmallImageUI
import com.adobe.marketing.mobile.aepcomposeui.style.AepCardStyle
import com.adobe.marketing.mobile.aepcomposeui.style.AepRowStyle
import com.adobe.marketing.mobile.aepcomposeui.style.AepTextStyle
import com.adobe.marketing.mobile.aepcomposeui.style.SmallImageUIStyle
import com.adobe.marketing.mobile.aepcomposeui.viewmodel.AepContentCardViewModel
import com.adobe.marketing.mobile.aepcomposeui.viewmodel.factory.AepContentCardViewModelFactory
import com.adobe.marketing.mobile.messaging.ContentCardEventObserver
import com.adobe.marketing.mobile.messaging.ContentCardMapper
import com.adobe.marketing.mobile.messaging.ContentCardUIEventListener
import com.adobe.marketing.mobile.messaging.ContentCardUIProvider
import com.adobe.marketing.mobile.messaging.Surface

@Composable
fun ContentCardComposable(
surface: Surface,
viewModel: AepContentCardViewModel,
comparator: Comparator<AepUI<*, *>>?,
callback: ContentCardUIEventListener?,
) {

// Initialize the ContentCardUIProvider
val contentCardUIProvider = ContentCardUIProvider(surface)


// Collect the state from ViewModel
val aepUiList by viewModel.aepUIList.collectAsStateWithLifecycle()

// Get the ContentCardSchemaData for the AepUI list if needed
val contentCardSchemaDataList = aepUiList.map {
when (it) {
is SmallImageUI ->
ContentCardMapper.instance.getContentCardSchemaData(it.getTemplate().id)

else -> null
}
}

// Reorder the AepUI list based on the ContentCardSchemaData fields if needed
val reorderedAepUIList = aepUiList.sortedWith(compareByDescending {
val rank =
contentCardSchemaDataList[aepUiList.indexOf(it)]?.meta?.get("priority") as String?
?: "0"
rank.toInt()
})

val smallImageCardStyleRow = SmallImageUIStyle.Builder()
.cardStyle(AepCardStyle(modifier = Modifier
.width(400.dp)
.height(200.dp)))
.rootRowStyle(
AepRowStyle(
modifier = Modifier.fillMaxSize()
)
)
.titleAepTextStyle(AepTextStyle(textStyle = TextStyle(Color.Green)))
.build()

// Create row with composables from AepUI instances
LazyRow {
items(reorderedAepUIList) { aepUI ->
when (aepUI) {
is SmallImageUI -> {
//TODO check if the card is dismissed to return or not
val state = aepUI.getState()
if (!state.dismissed) {
SmallImageCard(
ui = aepUI,
style = smallImageCardStyleRow,
observer = ContentCardEventObserver(callback)
)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2024 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.aepcomposeui.viewmodel

import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.adobe.marketing.mobile.aepcomposeui.AepUI
import com.adobe.marketing.mobile.messaging.ContentCardUIProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class AepContentCardViewModel(private val contentCardUIProvider: ContentCardUIProvider) : ViewModel() {
// State to hold AepUI list
private val _aepUIList = MutableStateFlow<List<AepUI<*, *>>>(emptyList())
val aepUIList: StateFlow<List<AepUI<*, *>>> = _aepUIList.asStateFlow()

init {
viewModelScope.launch {
contentCardUIProvider.getContentCardUI().collect { aepUiResult ->
aepUiResult.onSuccess { aepUi ->
_aepUIList.value = aepUi
}
aepUiResult.onFailure { throwable ->
Log.d("ContentCardUIProvider", "Error fetching AepUI list: $throwable")
}
}
}
}

fun refreshContent() {
viewModelScope.launch {
contentCardUIProvider.refreshContent()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
Copyright 2024 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.aepcomposeui.viewmodel.factory

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.adobe.marketing.mobile.aepcomposeui.viewmodel.AepContentCardViewModel
import com.adobe.marketing.mobile.messaging.ContentCardUIProvider

class AepContentCardViewModelFactory(
private val contentCardUIProvider: ContentCardUIProvider
) : ViewModelProvider.Factory {

@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return when {
modelClass.isAssignableFrom(AepContentCardViewModel::class.java) -> {
AepContentCardViewModel(contentCardUIProvider) as T
}
else -> throw IllegalArgumentException("Unknown ViewModel class")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
Copyright 2024 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package com.adobe.marketing.mobile.aepcomposeui.viewmodel

import com.adobe.marketing.mobile.aepcomposeui.AepUI
import com.adobe.marketing.mobile.messaging.ContentCardUIProvider
import com.adobe.marketing.mobile.messaging.Surface
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@OptIn(ExperimentalCoroutinesApi::class)
class AepContentCardViewModelTest {
/*private val testDispatcher = StandardTestDispatcher()

@MockK
private lateinit var surface: Surface
private lateinit var contentCardUIProvider: ContentCardUIProvider
private lateinit var viewModel: AepContentCardViewModel

@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
MockKAnnotations.init(this)
every { surface.isValid() } returns true
every { surface.uri } returns "test://surface"
every { surface.toEventData() } returns mapOf("uri" to "test://surface")
contentCardUIProvider = ContentCardUIProvider(surface)
viewModel = AepContentCardViewModel(contentCardUIProvider)
}*/

/*@After
fun tearDown() {
Dispatchers.resetMain()
}

@Test
fun `test getContentCardUI returns initial empty flow`() = runTest {
viewModel.
val result = contentCardUIProvider.getContentCardUI().first()
assert(result.getOrNull()?.isEmpty() == true)
}

@Test
fun `test refreshContent updates content`() = runTest {
contentCardUIProvider.refreshContent()
val result = contentCardUIProvider.getContentCardUI().first()
assert(result.isSuccess)
}

@Test
fun `test with invalid surface`() = runTest {
every { surface.isValid() } returns false
val result = contentCardUIProvider.getContentCardUI().first()
assert(result.getOrNull()?.isEmpty() == true)
}

@Test
fun `test with empty surface uri`() = runTest {
every { surface.uri } returns ""
every { surface.toEventData() } returns mapOf("uri" to "")
val result = contentCardUIProvider.getContentCardUI().first()
assert(result.getOrNull()?.isEmpty() == true)
}

@Test
fun `test with null event data`() = runTest {
every { surface.toEventData() } returns null
val result = contentCardUIProvider.getContentCardUI().first()
assert(result.getOrNull()?.isEmpty() == true)
}

@Test
fun `test with empty event data`() = runTest {
every { surface.toEventData() } returns emptyMap()
val result = contentCardUIProvider.getContentCardUI().first()
assert(result.getOrNull()?.isEmpty() == true)
}

@Test
fun initial_state_has_empty_aep_ui_list() {
//val mockContentCardUIProvider = mockk<ContentCardUIProvider>()
val viewModel = AepContentCardViewModel(contentCardUIProvider)
val initialList = viewModel.aepUIList.value
assertTrue(initialList.isEmpty())
}*/

/*@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()

private val testDispatcher = StandardTestDispatcher()

private lateinit var contentCardUIProvider: ContentCardUIProvider
private lateinit var viewModel: AepContentCardViewModel

@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
contentCardUIProvider = mockk()
viewModel = AepContentCardViewModel(contentCardUIProvider)
}

@Test
fun `test getContentCardUI success path`() = runTest {
val mockList = listOf<AepUI<*, *>>(mockk(), mockk())
val successFlow = flow {
emit(Result.success(mockList))
}

every { contentCardUIProvider.getContentCardUI() } returns successFlow

viewModel = AepContentCardViewModel(contentCardUIProvider)

testDispatcher.scheduler.advanceUntilIdle()

assertEquals(mockList, viewModel.aepUIList.value)
verify { contentCardUIProvider.getContentCardUI() }
}

@Test
fun `test getContentCardUI failure path`() = runTest {
val mockError = Exception("Test exception")
val failureFlow = flow<Result<List<AepUI<*, *>>>> {
emit(Result.failure(mockError))
}

every { contentCardUIProvider.getContentCardUI() } returns failureFlow

viewModel = AepContentCardViewModel(contentCardUIProvider)

testDispatcher.scheduler.advanceUntilIdle()

assertEquals(emptyList<AepUI<*, *>>(), viewModel.aepUIList.value)
verify { contentCardUIProvider.getContentCardUI() }
}

@Test
fun `test refreshContent`() = runTest {
coEvery { contentCardUIProvider.refreshContent() } just Runs

viewModel.refreshContent()

coVerify { contentCardUIProvider.refreshContent() }
}

@After
fun tearDown() {
Dispatchers.resetMain()
}*/

// Successful content fetch should update aepUIList with new AepUI items
@Test
fun successful_content_fetch_updates_aep_ui_list() = runTest {
Dispatchers.setMain(StandardTestDispatcher())
val mockContentCardUIProvider = mockk<ContentCardUIProvider>()
val mockAepUI = mockk<AepUI<*, *>>()
val expectedList = listOf(mockAepUI)
val flow = flowOf(Result.success(expectedList))

coEvery { mockContentCardUIProvider.getContentCardUI() } returns flow

val viewModel = AepContentCardViewModel(mockContentCardUIProvider)

advanceUntilIdle()

assertEquals(expectedList, viewModel.aepUIList.value)
}
}

Loading
Loading