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
@@ -0,0 +1,69 @@
package com.nativeapptemplate.nativeapptemplatefree

import com.nativeapptemplate.nativeapptemplatefree.model.UserData
import com.nativeapptemplate.nativeapptemplatefree.testing.repository.TestLoginRepository
import com.nativeapptemplate.nativeapptemplatefree.testing.util.MainDispatcherRule
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class MainActivityViewModelTest {
@get:Rule
val dispatcherRule = MainDispatcherRule()

private val loginRepository = TestLoginRepository()

private lateinit var viewModel: MainActivityViewModel

@Before
fun setUp() {
viewModel = MainActivityViewModel(loginRepository = loginRepository)
}

@Test
fun uiState_initialValue_isLoading() = runTest {
assertEquals(MainActivityUiState.Loading, viewModel.uiState.value)
assertFalse(viewModel.uiState.value.isLoggedIn)
}

@Test
fun uiState_emitsSuccess_whenUserDataArrives() = runTest {
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }

val userData = UserData(isLoggedIn = true, email = "john@example.com")
loginRepository.sendUserData(userData)

val state = viewModel.uiState.value
assertTrue(state is MainActivityUiState.Success)
assertEquals(userData, (state as MainActivityUiState.Success).userData)
assertTrue(state.isLoggedIn)
}

@Test
fun uiState_isLoggedInReflectsUserData() = runTest {
backgroundScope.launch(UnconfinedTestDispatcher()) { viewModel.uiState.collect() }

loginRepository.sendUserData(UserData(isLoggedIn = false))
assertFalse(viewModel.uiState.value.isLoggedIn)

loginRepository.sendUserData(UserData(isLoggedIn = true))
assertTrue(viewModel.uiState.value.isLoggedIn)
}

@Test
fun updateDidShowTapShopBelowTip_persistsValue() = runTest {
loginRepository.sendUserData(UserData())

viewModel.updateDidShowTapShopBelowTip(true)

assertTrue(loginRepository.userData.first().didShowTapShopBelowTip)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package com.nativeapptemplate.nativeapptemplatefree.network

import com.nativeapptemplate.nativeapptemplatefree.UserPreferences
import com.nativeapptemplate.nativeapptemplatefree.datastore.NatPreferencesDataSource
import com.nativeapptemplate.nativeapptemplatefree.datastoreTest.InMemoryDataStore
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import okhttp3.Call
import okhttp3.Connection
import okhttp3.Interceptor
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import java.util.concurrent.TimeUnit

class AuthInterceptorTest {
private val testScope = TestScope(UnconfinedTestDispatcher())

private fun dataSourceWith(
token: String,
client: String,
uid: String,
expiry: String,
): NatPreferencesDataSource {
val initial = UserPreferences.newBuilder()
.setToken(token)
.setClient(client)
.setUid(uid)
.setExpiry(expiry)
.build()
return NatPreferencesDataSource(InMemoryDataStore(initial))
}

@Test
fun intercept_withAuthData_addsAuthHeaders() = testScope.runTest {
val dataSource = dataSourceWith(
token = "test-token",
client = "test-client",
uid = "john@example.com",
expiry = "12345",
)
val interceptor = AuthInterceptor(dataSource)
val chain = RecordingChain(Request.Builder().url("https://example.com/").build())

interceptor.intercept(chain)

val sent = chain.proceededRequest!!
assertEquals("test-token", sent.header("access-token"))
assertEquals("Bearer", sent.header("token-type"))
assertEquals("test-client", sent.header("client"))
assertEquals("12345", sent.header("expiry"))
assertEquals("john@example.com", sent.header("uid"))
}

@Test
fun intercept_withAuthData_addsBaseHeaders() = testScope.runTest {
val dataSource = dataSourceWith(
token = "test-token",
client = "test-client",
uid = "john@example.com",
expiry = "12345",
)
val interceptor = AuthInterceptor(dataSource)
val chain = RecordingChain(Request.Builder().url("https://example.com/").build())

interceptor.intercept(chain)

val sent = chain.proceededRequest!!
assertEquals("android", sent.header("source"))
assertEquals("application/vnd.api+json; charset=utf-8", sent.header("Accept"))
assertEquals("application/json", sent.header("Content-Type"))
}

@Test
fun intercept_withoutAuthData_omitsAuthHeaders() = testScope.runTest {
val dataSource = NatPreferencesDataSource(
InMemoryDataStore(UserPreferences.getDefaultInstance()),
)
val interceptor = AuthInterceptor(dataSource)
val chain = RecordingChain(Request.Builder().url("https://example.com/").build())

interceptor.intercept(chain)

val sent = chain.proceededRequest!!
assertNull(sent.header("access-token"))
assertNull(sent.header("token-type"))
assertNull(sent.header("client"))
assertNull(sent.header("expiry"))
assertNull(sent.header("uid"))
assertEquals("android", sent.header("source"))
}

@Test
fun intercept_preservesOriginalRequestUrl() = testScope.runTest {
val dataSource = NatPreferencesDataSource(
InMemoryDataStore(UserPreferences.getDefaultInstance()),
)
val interceptor = AuthInterceptor(dataSource)
val originalUrl = "https://example.com/path?query=value"
val chain = RecordingChain(Request.Builder().url(originalUrl).build())

interceptor.intercept(chain)

assertEquals(originalUrl, chain.proceededRequest!!.url.toString())
}
}

private class RecordingChain(private val request: Request) : Interceptor.Chain {
var proceededRequest: Request? = null

override fun request(): Request = request

override fun proceed(request: Request): Response {
proceededRequest = request
return Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("OK")
.body("".toResponseBody(null))
.build()
}

override fun connection(): Connection? = null
override fun call(): Call = error("not used")
override fun connectTimeoutMillis(): Int = 0
override fun withConnectTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain = error("not used")
override fun readTimeoutMillis(): Int = 0
override fun withReadTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain = error("not used")
override fun writeTimeoutMillis(): Int = 0
override fun withWriteTimeout(timeout: Int, unit: TimeUnit): Interceptor.Chain = error("not used")
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ class TestLoginRepository : LoginRepository {
}

override suspend fun setDidShowTapShopBelowTip(didShowTapShopBelowTip: Boolean) {
currentUserData.let { current ->
_userData.tryEmit(current.copy(didShowTapShopBelowTip = didShowTapShopBelowTip))
}
}

override suspend fun setIsEmailUpdated(isEmailUpdated: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.nativeapptemplate.nativeapptemplatefree.ui.app_root

import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Test

class OnboardingTest {
@Test
fun onboarding_defaultsToLandscape() {
val onboarding = Onboarding(id = 1)
assertEquals(ImageOrientation.LANDSCAPE, onboarding.imageOrientation)
}

@Test
fun onboarding_acceptsExplicitOrientation() {
val onboarding = Onboarding(id = 2, imageOrientation = ImageOrientation.PORTRAIT)
assertEquals(ImageOrientation.PORTRAIT, onboarding.imageOrientation)
}

@Test
fun onboarding_equalsWhenIdAndOrientationMatch() {
val a = Onboarding(id = 3, imageOrientation = ImageOrientation.PORTRAIT)
val b = Onboarding(id = 3, imageOrientation = ImageOrientation.PORTRAIT)
assertEquals(a, b)
}

@Test
fun onboarding_differsWhenOrientationDiffers() {
val a = Onboarding(id = 4, imageOrientation = ImageOrientation.PORTRAIT)
val b = Onboarding(id = 4, imageOrientation = ImageOrientation.LANDSCAPE)
assertNotEquals(a, b)
}

@Test
fun imageOrientation_hasPortraitAndLandscape() {
val values = ImageOrientation.entries.map { it.name }
assertEquals(setOf("PORTRAIT", "LANDSCAPE"), values.toSet())
}
}
Loading