Skip to content

Commit 2d056c2

Browse files
committed
fix(ui): Don't use 0 as the initial value for Skip.
This patch fixes an issue where 0 was used as the initial value for the `Skip` higher-order stream in the `TimelineSubscriber`. This is wrong, as the `SkipCount` value may have been modified before the `TimelineSubscriber` is created. This patch provides a test to reproduce the problem.
1 parent 962671d commit 2d056c2

File tree

2 files changed

+80
-6
lines changed

2 files changed

+80
-6
lines changed

crates/matrix-sdk-ui/src/timeline/subscriber.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,12 @@ impl TimelineSubscriber {
8787
let (initial_values, stream) = observable_items
8888
.subscribe()
8989
.into_values_and_batched_stream()
90-
.dynamic_skip_with_initial_count(0, observable_skip_count.subscribe());
90+
.dynamic_skip_with_initial_count(
91+
// The `SkipCount` value may have been modified before the subscriber is
92+
// created. Let's use the current value instead of hardcoding it to 0.
93+
observable_skip_count.get(),
94+
observable_skip_count.subscribe(),
95+
);
9196

9297
(initial_values, Self { inner: stream })
9398
}
@@ -231,6 +236,11 @@ pub mod skip {
231236
self.count.get()
232237
}
233238

239+
/// Get the current count value.
240+
pub fn get(&self) -> usize {
241+
self.count.get()
242+
}
243+
234244
/// Subscribe to update of the count value.
235245
pub fn subscribe(&self) -> Subscriber<usize> {
236246
self.count.subscribe()

crates/matrix-sdk-ui/tests/integration/timeline/mod.rs

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use std::time::Duration;
15+
use std::{ops::Not, time::Duration};
1616

1717
use assert_matches::assert_matches;
1818
use assert_matches2::assert_let;
@@ -24,7 +24,7 @@ use matrix_sdk::{
2424
};
2525
use matrix_sdk_test::{
2626
async_test, event_factory::EventFactory, mocks::mock_encryption_state, sync_timeline_event,
27-
JoinedRoomBuilder, RoomAccountDataTestEvent, StateTestEvent, SyncResponseBuilder, BOB,
27+
JoinedRoomBuilder, RoomAccountDataTestEvent, StateTestEvent, SyncResponseBuilder, ALICE, BOB,
2828
};
2929
use matrix_sdk_ui::{
3030
timeline::{
@@ -36,9 +36,10 @@ use matrix_sdk_ui::{
3636
use ruma::{
3737
event_id,
3838
events::room::{encryption::RoomEncryptionEventContent, message::RoomMessageEventContent},
39-
owned_event_id, room_id, user_id, MilliSecondsSinceUnixEpoch,
39+
owned_event_id, room_id, user_id, EventId, MilliSecondsSinceUnixEpoch,
4040
};
4141
use serde_json::json;
42+
use sliding_sync::assert_timeline_stream;
4243
use stream_assert::assert_pending;
4344
use wiremock::{
4445
matchers::{header, method, path_regex},
@@ -831,8 +832,71 @@ async fn test_timeline_without_encryption_can_update() {
831832
assert_pending!(stream);
832833
}
833834

835+
#[async_test]
836+
async fn test_timeline_receives_a_limited_number_of_events_when_subscribing() {
837+
let room_id = room_id!("!foo:bar.baz");
838+
let event_factory = EventFactory::new().room(room_id).sender(&ALICE);
839+
840+
let mock_server = MatrixMockServer::new().await;
841+
let client = mock_server.client_builder().build().await;
842+
843+
mock_server.sync_joined_room(&client, room_id).await;
844+
845+
let event_cache = client.event_cache();
846+
event_cache.subscribe().unwrap();
847+
848+
// The event cache contains 30 events.
849+
event_cache
850+
.add_initial_events(
851+
room_id,
852+
(0..30)
853+
.map(|nth| {
854+
event_factory
855+
.text_msg("foo")
856+
.event_id(&EventId::parse(format!("$ev{nth}")).unwrap())
857+
.into_event()
858+
})
859+
.collect::<Vec<_>>(),
860+
None,
861+
)
862+
.await
863+
.unwrap();
864+
865+
let room = client.get_room(room_id).unwrap();
866+
867+
// The timeline is created.
868+
let timeline = room.timeline().await.unwrap();
869+
let (timeline_initial_items, mut timeline_stream) = timeline.subscribe().await;
870+
871+
// The timeline receives 20 initial values, not 30!
872+
assert_eq!(timeline_initial_items.len(), 20);
873+
assert_pending!(timeline_stream);
874+
875+
// To get the other, the timeline needs to paginate.
876+
877+
let _no_network_pagination =
878+
mock_server.mock_room_messages().error500().never().mount_as_scoped().await;
879+
880+
// Now let's do a backwards pagination of 5 items.
881+
let hit_end_of_timeline = timeline.paginate_backwards(5).await.unwrap();
882+
883+
assert!(hit_end_of_timeline.not());
884+
885+
// Oh, 5 new items, without even hitting the network because the timeline
886+
// already has these!
887+
assert_timeline_stream! {
888+
[timeline_stream]
889+
prepend "$ev9";
890+
prepend "$ev8";
891+
prepend "$ev7";
892+
prepend "$ev6";
893+
prepend "$ev5";
894+
};
895+
assert_pending!(timeline_stream);
896+
}
897+
834898
struct PinningTestSetup<'a> {
835-
event_id: &'a ruma::EventId,
899+
event_id: &'a EventId,
836900
room_id: &'a ruma::RoomId,
837901
client: matrix_sdk::Client,
838902
server: wiremock::MockServer,
@@ -904,7 +968,7 @@ impl PinningTestSetup<'_> {
904968
let _response = self.client.sync_once(self.sync_settings.clone()).await.unwrap();
905969
}
906970

907-
fn event_id(&self) -> &ruma::EventId {
971+
fn event_id(&self) -> &EventId {
908972
self.event_id
909973
}
910974
}

0 commit comments

Comments
 (0)