Skip to content

Commit

Permalink
[#21] Add platform date
Browse files Browse the repository at this point in the history
  • Loading branch information
blyscuit committed Dec 16, 2022
1 parent 660997d commit ccccccf
Show file tree
Hide file tree
Showing 16 changed files with 336 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package co.nimblehq.blisskmmic.domain.platform.datetime

actual class DateTimeFormatterImpl: DateTimeFormatter {

actual override fun getFormattedString(
secondsSinceEpoch: Long,
format: DateFormat
): String {
// TODO: implement date for Android
return ""
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class UserApiModel(
@SerialName("avatar_url") val avatarUrl: String
) {

fun toUser() = User(
fun toUser(): User = User(
name,
avatarUrl
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface NetworkDataSource {
fun logIn(target: LoginTargetType): Flow<TokenApiModel>
fun resetPassword(target: ResetPasswordTargetType): Flow<ResetPasswordMeta>
fun survey(target: SurveySelectionTargetType): Flow<Pair<List<SurveyApiModel>, PaginationMetaApiModel>>
fun getProfile(target: UserProfileTargetType): Flow<UserApiModel>
fun profile(target: UserProfileTargetType): Flow<UserApiModel>
}

class NetworkDataSourceImpl(private val networkClient: NetworkClient): NetworkDataSource {
Expand All @@ -36,7 +36,7 @@ class NetworkDataSourceImpl(private val networkClient: NetworkClient): NetworkDa
return networkClient.fetchWithMeta(target.requestBuilder())
}

override fun getProfile(target: UserProfileTargetType): Flow<UserApiModel> {
override fun profile(target: UserProfileTargetType): Flow<UserApiModel> {
return networkClient.fetch(target.requestBuilder())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package co.nimblehq.blisskmmic.data.network.target
import co.nimblehq.blisskmmic.data.network.helpers.TargetType
import io.ktor.http.*

class UserProfileTargetType(): TargetType<Unit> {
class UserProfileTargetType: TargetType<Unit> {

override var path = "me"
override var method = HttpMethod.Get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class UserRepositoryImpl(

override fun getProfile(): Flow<User> {
return networkDataSource
.getProfile(UserProfileTargetType())
.profile(UserProfileTargetType())
.map { it.toUser() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package co.nimblehq.blisskmmic.domain.platform.datetime

sealed class DateFormat(val value: String) {
object DayOfWeekMonthDay: DateFormat("EEEE, MMMM d")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package co.nimblehq.blisskmmic.domain.platform.datetime

interface DateTimeFormatter {

fun getFormattedString(secondsSinceEpoch: Long, format: DateFormat): String
}

expect class DateTimeFormatterImpl(): DateTimeFormatter {

override fun getFormattedString(secondsSinceEpoch: Long, format: DateFormat): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import co.nimblehq.blisskmmic.data.network.datasource.NetworkDataSourceImpl
import co.nimblehq.blisskmmic.data.network.target.LoginTargetType
import co.nimblehq.blisskmmic.data.network.target.ResetPasswordTargetType
import co.nimblehq.blisskmmic.data.network.target.SurveySelectionTargetType
import co.nimblehq.blisskmmic.helpers.json.ERROR_JSON_RESULT
import co.nimblehq.blisskmmic.helpers.json.LOG_IN_JSON_RESULT
import co.nimblehq.blisskmmic.helpers.json.RESET_PASSWORD_JSON_RESULT
import co.nimblehq.blisskmmic.helpers.json.SURVEY_LIST_JSON_RESULT
import co.nimblehq.blisskmmic.data.network.target.UserProfileTargetType
import co.nimblehq.blisskmmic.helpers.json.*
import co.nimblehq.blisskmmic.helpers.mock.ktor.jsonMockEngine
import co.nimblehq.jsonapi.model.JsonApiException
import io.kotest.matchers.collections.shouldContain
Expand Down Expand Up @@ -101,4 +99,21 @@ class NetworkDataSourceTest {
}
}
}

// Profile

@Test
fun `When calling profile with success response, it returns correct object`() = runTest {
val engine = jsonMockEngine(USER_PROFILE_JSON_RESULT, "me")
val networkClient = NetworkClient(engine = engine)
val dataSource = NetworkDataSourceImpl(networkClient)
dataSource
.profile(UserProfileTargetType())
.test {
val response = awaitItem()
response.email shouldBe "mail@mail.com"
response.name shouldBe "Name"
awaitComplete()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package co.nimblehq.blisskmmic.data.repository

import app.cash.turbine.test
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.*
import org.kodein.mock.Mocker
import org.kodein.mock.UsesMocks
import kotlin.test.BeforeTest
import kotlin.test.Test

@UsesMocks(Clock::class)
@ExperimentalCoroutinesApi
class DeviceInfoRepositoryTest {

private val mocker = Mocker()
private val clock = MockClock(mocker)
private val instant = Instant.DISTANT_PAST
private val deviceInfoRepository = DeviceInfoRepositoryImpl(clock)

@BeforeTest
fun setUp() {
mocker.reset()
}

@Test
fun `When calling profile with success response, it returns correct object`() = runTest {
mocker.every {
clock.now()
} returns instant
deviceInfoRepository
.getCurrentDate()
.test {
val item = awaitItem()
val local = instant.toLocalDateTime(TimeZone.currentSystemDefault())
item.day shouldBe local.dayOfMonth
item.month shouldBe local.monthNumber
item.dayOfWeek shouldBe local.dayOfWeek.isoDayNumber
awaitComplete()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package co.nimblehq.blisskmmic.data.repository

import app.cash.turbine.test
import co.nimblehq.blisskmmic.data.model.UserApiModel
import co.nimblehq.blisskmmic.data.model.fakeUserApiModel
import co.nimblehq.blisskmmic.data.network.datasource.MockNetworkDataSource
import co.nimblehq.blisskmmic.data.network.datasource.NetworkDataSource
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.kodein.mock.Mocker
import org.kodein.mock.UsesFakes
import org.kodein.mock.UsesMocks
import kotlin.test.BeforeTest
import kotlin.test.Test

@UsesMocks(NetworkDataSource::class)
@UsesFakes(UserApiModel::class)
@ExperimentalCoroutinesApi
class UserRepositoryTest {

private val mocker = Mocker()
private val networkDataSource = MockNetworkDataSource(mocker)
private val user = fakeUserApiModel()
private val userRepository = UserRepositoryImpl(networkDataSource)

@BeforeTest
fun setUp() {
mocker.reset()
}

@Test
fun `When calling getProfile with success response, it returns correct object`() = runTest {
mocker.every {
networkDataSource.profile(isAny())
} returns flowOf(user)
userRepository
.getProfile()
.test {
val item = awaitItem()
item.name shouldBe user.name
item.avatarUrl shouldBe user.avatarUrl
awaitComplete()
}
}

@Test
fun `When calling getProfile with failure response, it returns correct error`() = runTest {
mocker.every {
networkDataSource.profile(isAny())
} returns flow { error("Fail") }
userRepository
.getProfile()
.test {
awaitError().message shouldBe "Fail"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package co.nimblehq.blisskmmic.domain.usecase

import app.cash.turbine.test
import co.nimblehq.blisskmmic.domain.model.DateComponents
import co.nimblehq.blisskmmic.domain.repository.DeviceInfoRepository
import co.nimblehq.blisskmmic.domain.repository.MockDeviceInfoRepository
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.kodein.mock.Mocker
import org.kodein.mock.UsesMocks
import kotlin.test.BeforeTest
import kotlin.test.Test

@UsesMocks(DeviceInfoRepository::class)
@ExperimentalCoroutinesApi
class GetCurrentDateUseCaseTest {

private val mocker = Mocker()
private val deviceInfoRepository = MockDeviceInfoRepository(mocker)
private val getCurrentDateUseCase = GetCurrentDateUseCaseImpl(deviceInfoRepository)
private val dateComponents = DateComponents(day = 2, month = 2, dayOfWeek = 3)

@BeforeTest
fun setUp() {
mocker.reset()
}

@Test
fun `When calling getCurrent with a success response, it returns correct object`() = runTest {
mocker.every {
deviceInfoRepository.getCurrentDate()
} returns flowOf(dateComponents)

getCurrentDateUseCase()
.test {
val item = awaitItem()
item.day shouldBe 2
item.dayOfWeek shouldBe "WEDNESDAY"
item.month shouldBe "FEBRUARY"
awaitComplete()
}
}

@Test
fun `When calling getCurrent with a failure response, it returns correct error`() = runTest {
mocker.every {
deviceInfoRepository.getCurrentDate()
} returns flow { error("Fail") }

getCurrentDateUseCase()
.test {
awaitError().message shouldBe "Fail"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package co.nimblehq.blisskmmic.domain.usecase

import app.cash.turbine.test
import co.nimblehq.blisskmmic.domain.model.PaginationMeta
import co.nimblehq.blisskmmic.domain.model.Survey
import co.nimblehq.blisskmmic.domain.model.User
import co.nimblehq.blisskmmic.domain.model.fakeUser
import co.nimblehq.blisskmmic.domain.repository.MockUserRepository
import co.nimblehq.blisskmmic.domain.repository.SurveyRepository
import co.nimblehq.blisskmmic.domain.repository.UserRepository
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.kodein.mock.Mocker
import org.kodein.mock.UsesFakes
import org.kodein.mock.UsesMocks
import kotlin.test.BeforeTest
import kotlin.test.Test

@OptIn(ExperimentalCoroutinesApi::class)
@UsesMocks(UserRepository::class)
@UsesFakes(User::class)
class GetProfileUseCaseTest {

private val mocker = Mocker()
private val userRepository = MockUserRepository(mocker)
private val user = fakeUser()
private val getProfileUseCase = GetProfileUseCaseImpl(userRepository)

@BeforeTest
fun setUp() {
mocker.reset()
}

@Test
fun `When calling getProfile with a success response, it returns correct object`() = runTest {
mocker.every {
userRepository.getProfile()
} returns flowOf(user)

getProfileUseCase()
.test {
val item = awaitItem()
item.name shouldBe user.name
item.avatarUrl shouldBe user.avatarUrl
awaitComplete()
}
}

@Test
fun `When calling getProfile with a failure response, it returns correct error`() = runTest {
mocker.every {
userRepository.getProfile()
} returns flow { error("Fail") }

getProfileUseCase()
.test {
awaitError().message shouldBe "Fail"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package co.nimblehq.blisskmmic.helpers.json

const val USER_PROFILE_JSON_RESULT = """
{
"data": {
"id": "1",
"type": "user",
"attributes": {
"name": "Name",
"email": "mail@mail.com",
"avatar_url": "https://api.adorable.io/avatar/mail@mail.com"
}
}
}
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package co.nimblehq.blisskmmic.domain.platform.datetime

import platform.Foundation.*

class DateFormatter {

companion object {

var dateFormatters:MutableMap<DateFormat, NSDateFormatter> = mutableMapOf()

fun get(dateFormat: DateFormat): NSDateFormatter {
return initializeIfNeeded(dateFormat)
}

private fun dateFormatter(dataFormat: DateFormat): NSDateFormatter {
return NSDateFormatter()
.also {
it.dateFormat = dataFormat.value
}
}

private fun initializeIfNeeded(dateFormat: DateFormat): NSDateFormatter {
dateFormatters[dateFormat]?.let { return it }
val dateFormatter = dateFormatter(dateFormat)
dateFormatters[dateFormat] = dateFormatter
return dateFormatter
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package co.nimblehq.blisskmmic.domain.platform.datetime

import platform.Foundation.NSDate

actual class DateTimeFormatterImpl: DateTimeFormatter {

actual override fun getFormattedString(
secondsSinceEpoch: Long,
format: DateFormat
): String {
val date = NSDate(secondsSinceEpoch.toDouble())
return DateFormatter.get(format).stringFromDate(date)
}
}
Loading

0 comments on commit ccccccf

Please sign in to comment.