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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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