Browse Source
* Add `DateTimeObserver` to rebuild the room summary data when the date/time changes. * Add time changed action too, to trigger when the user manually changes date/time * Fix timezone issue by adding `TimezoneProvider`, fix tests * Create test for `DateTimeObserver` usage in `RoomListDataSource` * Create aRoomListRoomSummaryFactory function. * Improve test by faking the lastMessageTimestampFormatter --------- Co-authored-by: Benoit Marty <benoit@matrix.org>pull/3673/head
Jorge Martin Espinosa
2 days ago
committed by
GitHub
16 changed files with 262 additions and 26 deletions
@ -0,0 +1,106 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2024 New Vector Ltd. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only |
||||||
|
* Please see LICENSE in the repository root for full details. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.element.android.features.roomlist.impl.datasource |
||||||
|
|
||||||
|
import app.cash.turbine.test |
||||||
|
import com.google.common.truth.Truth.assertThat |
||||||
|
import io.element.android.features.roomlist.impl.FakeDateTimeObserver |
||||||
|
import io.element.android.libraries.androidutils.system.DateTimeObserver |
||||||
|
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter |
||||||
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService |
||||||
|
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService |
||||||
|
import io.element.android.libraries.matrix.test.room.aRoomSummary |
||||||
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService |
||||||
|
import io.element.android.tests.testutils.testCoroutineDispatchers |
||||||
|
import kotlinx.coroutines.test.TestScope |
||||||
|
import kotlinx.coroutines.test.runTest |
||||||
|
import org.junit.Test |
||||||
|
import java.time.Instant |
||||||
|
|
||||||
|
class RoomListDataSourceTest { |
||||||
|
@Test |
||||||
|
fun `when DateTimeObserver gets a date change, the room summaries are refreshed`() = runTest { |
||||||
|
val roomListService = FakeRoomListService().apply { |
||||||
|
postState(RoomListService.State.Running) |
||||||
|
postAllRooms(listOf(aRoomSummary())) |
||||||
|
} |
||||||
|
val dateTimeObserver = FakeDateTimeObserver() |
||||||
|
val lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter() |
||||||
|
lastMessageTimestampFormatter.givenFormat("Today") |
||||||
|
val roomListDataSource = createRoomListDataSource( |
||||||
|
roomListService = roomListService, |
||||||
|
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory( |
||||||
|
lastMessageTimestampFormatter = lastMessageTimestampFormatter, |
||||||
|
), |
||||||
|
dateTimeObserver = dateTimeObserver, |
||||||
|
) |
||||||
|
|
||||||
|
roomListDataSource.allRooms.test { |
||||||
|
// Observe room list items changes |
||||||
|
roomListDataSource.launchIn(backgroundScope) |
||||||
|
// Get the initial room list |
||||||
|
val initialRoomList = awaitItem() |
||||||
|
assertThat(initialRoomList).isNotEmpty() |
||||||
|
assertThat(initialRoomList.first().timestamp).isEqualTo("Today") |
||||||
|
lastMessageTimestampFormatter.givenFormat("Yesterday") |
||||||
|
// Trigger a date change |
||||||
|
dateTimeObserver.given(DateTimeObserver.Event.DateChanged(Instant.MIN, Instant.now())) |
||||||
|
// Check there is a new list and it's not the same as the previous one |
||||||
|
val newRoomList = awaitItem() |
||||||
|
assertThat(newRoomList).isNotSameInstanceAs(initialRoomList) |
||||||
|
assertThat(newRoomList.first().timestamp).isEqualTo("Yesterday") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `when DateTimeObserver gets a time zone change, the room summaries are refreshed`() = runTest { |
||||||
|
val roomListService = FakeRoomListService().apply { |
||||||
|
postState(RoomListService.State.Running) |
||||||
|
postAllRooms(listOf(aRoomSummary())) |
||||||
|
} |
||||||
|
val dateTimeObserver = FakeDateTimeObserver() |
||||||
|
val lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter() |
||||||
|
lastMessageTimestampFormatter.givenFormat("Today") |
||||||
|
val roomListDataSource = createRoomListDataSource( |
||||||
|
roomListService = roomListService, |
||||||
|
roomListRoomSummaryFactory = aRoomListRoomSummaryFactory( |
||||||
|
lastMessageTimestampFormatter = lastMessageTimestampFormatter, |
||||||
|
), |
||||||
|
dateTimeObserver = dateTimeObserver, |
||||||
|
) |
||||||
|
roomListDataSource.allRooms.test { |
||||||
|
// Observe room list items changes |
||||||
|
roomListDataSource.launchIn(backgroundScope) |
||||||
|
// Get the initial room list |
||||||
|
val initialRoomList = awaitItem() |
||||||
|
assertThat(initialRoomList).isNotEmpty() |
||||||
|
assertThat(initialRoomList.first().timestamp).isEqualTo("Today") |
||||||
|
lastMessageTimestampFormatter.givenFormat("Yesterday") |
||||||
|
// Trigger a timezone change |
||||||
|
dateTimeObserver.given(DateTimeObserver.Event.TimeZoneChanged) |
||||||
|
// Check there is a new list and it's not the same as the previous one |
||||||
|
val newRoomList = awaitItem() |
||||||
|
assertThat(newRoomList).isNotSameInstanceAs(initialRoomList) |
||||||
|
assertThat(newRoomList.first().timestamp).isEqualTo("Yesterday") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun TestScope.createRoomListDataSource( |
||||||
|
roomListService: FakeRoomListService = FakeRoomListService(), |
||||||
|
roomListRoomSummaryFactory: RoomListRoomSummaryFactory = aRoomListRoomSummaryFactory(), |
||||||
|
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), |
||||||
|
dateTimeObserver: FakeDateTimeObserver = FakeDateTimeObserver(), |
||||||
|
) = RoomListDataSource( |
||||||
|
roomListService = roomListService, |
||||||
|
roomListRoomSummaryFactory = roomListRoomSummaryFactory, |
||||||
|
coroutineDispatchers = testCoroutineDispatchers(), |
||||||
|
notificationSettingsService = notificationSettingsService, |
||||||
|
appScope = backgroundScope, |
||||||
|
dateTimeObserver = dateTimeObserver, |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2024 New Vector Ltd. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only |
||||||
|
* Please see LICENSE in the repository root for full details. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.element.android.features.roomlist.impl.datasource |
||||||
|
|
||||||
|
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter |
||||||
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter |
||||||
|
|
||||||
|
fun aRoomListRoomSummaryFactory( |
||||||
|
lastMessageTimestampFormatter: LastMessageTimestampFormatter = LastMessageTimestampFormatter { _ -> "Today" }, |
||||||
|
roomLastMessageFormatter: RoomLastMessageFormatter = RoomLastMessageFormatter { _, _ -> "Hey" } |
||||||
|
) = RoomListRoomSummaryFactory( |
||||||
|
lastMessageTimestampFormatter = lastMessageTimestampFormatter, |
||||||
|
roomLastMessageFormatter = roomLastMessageFormatter |
||||||
|
) |
@ -0,0 +1,61 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2024 New Vector Ltd. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only |
||||||
|
* Please see LICENSE in the repository root for full details. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.element.android.libraries.androidutils.system |
||||||
|
|
||||||
|
import android.content.BroadcastReceiver |
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.content.IntentFilter |
||||||
|
import com.squareup.anvil.annotations.ContributesBinding |
||||||
|
import io.element.android.libraries.androidutils.system.DateTimeObserver.Event |
||||||
|
import io.element.android.libraries.di.AppScope |
||||||
|
import io.element.android.libraries.di.ApplicationContext |
||||||
|
import io.element.android.libraries.di.SingleIn |
||||||
|
import kotlinx.coroutines.flow.Flow |
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow |
||||||
|
import java.time.Instant |
||||||
|
import javax.inject.Inject |
||||||
|
|
||||||
|
interface DateTimeObserver { |
||||||
|
val changes: Flow<Event> |
||||||
|
|
||||||
|
sealed interface Event { |
||||||
|
data object TimeZoneChanged : Event |
||||||
|
data class DateChanged(val previous: Instant, val new: Instant) : Event |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class) |
||||||
|
@SingleIn(AppScope::class) |
||||||
|
class DefaultDateTimeObserver @Inject constructor( |
||||||
|
@ApplicationContext context: Context |
||||||
|
) : DateTimeObserver { |
||||||
|
private val dateTimeReceiver = object : BroadcastReceiver() { |
||||||
|
private var lastTime = Instant.now() |
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) { |
||||||
|
val newDate = Instant.now() |
||||||
|
when (intent.action) { |
||||||
|
Intent.ACTION_TIMEZONE_CHANGED -> changes.tryEmit(Event.TimeZoneChanged) |
||||||
|
Intent.ACTION_DATE_CHANGED -> changes.tryEmit(Event.DateChanged(lastTime, newDate)) |
||||||
|
Intent.ACTION_TIME_CHANGED -> changes.tryEmit(Event.DateChanged(lastTime, newDate)) |
||||||
|
} |
||||||
|
lastTime = newDate |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override val changes = MutableSharedFlow<Event>(extraBufferCapacity = 10) |
||||||
|
|
||||||
|
init { |
||||||
|
context.registerReceiver(dateTimeReceiver, IntentFilter().apply { |
||||||
|
addAction(Intent.ACTION_TIMEZONE_CHANGED) |
||||||
|
addAction(Intent.ACTION_DATE_CHANGED) |
||||||
|
addAction(Intent.ACTION_TIME_CHANGED) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2024 New Vector Ltd. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only |
||||||
|
* Please see LICENSE in the repository root for full details. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.element.android.libraries.dateformatter.impl |
||||||
|
|
||||||
|
import kotlinx.datetime.TimeZone |
||||||
|
|
||||||
|
fun interface TimezoneProvider { |
||||||
|
fun provide(): TimeZone |
||||||
|
} |
Loading…
Reference in new issue