1use ruma::{OwnedRoomId, RoomId};
19
20use super::{ChunkContent, RawChunk};
21use crate::linked_chunk::{ChunkIdentifier, Position, Update};
22
23#[derive(Debug, PartialEq)]
25struct ChunkRow {
26 room_id: OwnedRoomId,
27 previous_chunk: Option<ChunkIdentifier>,
28 chunk: ChunkIdentifier,
29 next_chunk: Option<ChunkIdentifier>,
30}
31
32#[derive(Debug, PartialEq)]
34struct ItemRow<Item, Gap> {
35 room_id: OwnedRoomId,
36 position: Position,
37 item: Either<Item, Gap>,
38}
39
40#[derive(Debug, PartialEq)]
42enum Either<Item, Gap> {
43 Item(Item),
45
46 Gap(Gap),
48}
49
50#[derive(Debug)]
69pub struct RelationalLinkedChunk<Item, Gap> {
70 chunks: Vec<ChunkRow>,
72
73 items: Vec<ItemRow<Item, Gap>>,
75}
76
77impl<Item, Gap> RelationalLinkedChunk<Item, Gap> {
78 pub fn new() -> Self {
80 Self { chunks: Vec::new(), items: Vec::new() }
81 }
82
83 pub fn clear(&mut self) {
85 self.chunks.clear();
86 self.items.clear();
87 }
88
89 pub fn apply_updates(&mut self, room_id: &RoomId, updates: Vec<Update<Item, Gap>>) {
92 for update in updates {
93 match update {
94 Update::NewItemsChunk { previous, new, next } => {
95 insert_chunk(&mut self.chunks, room_id, previous, new, next);
96 }
97
98 Update::NewGapChunk { previous, new, next, gap } => {
99 insert_chunk(&mut self.chunks, room_id, previous, new, next);
100 self.items.push(ItemRow {
101 room_id: room_id.to_owned(),
102 position: Position::new(new, 0),
103 item: Either::Gap(gap),
104 });
105 }
106
107 Update::RemoveChunk(chunk_identifier) => {
108 remove_chunk(&mut self.chunks, room_id, chunk_identifier);
109
110 let indices_to_remove = self
111 .items
112 .iter()
113 .enumerate()
114 .filter_map(
115 |(nth, ItemRow { room_id: room_id_candidate, position, .. })| {
116 (room_id == room_id_candidate
117 && position.chunk_identifier() == chunk_identifier)
118 .then_some(nth)
119 },
120 )
121 .collect::<Vec<_>>();
122
123 for index_to_remove in indices_to_remove.into_iter().rev() {
124 self.items.remove(index_to_remove);
125 }
126 }
127
128 Update::PushItems { mut at, items } => {
129 for item in items {
130 self.items.push(ItemRow {
131 room_id: room_id.to_owned(),
132 position: at,
133 item: Either::Item(item),
134 });
135 at.increment_index();
136 }
137 }
138
139 Update::ReplaceItem { at, item } => {
140 let existing = self
141 .items
142 .iter_mut()
143 .find(|item| item.position == at)
144 .expect("trying to replace at an unknown position");
145 assert!(
146 matches!(existing.item, Either::Item(..)),
147 "trying to replace a gap with an item"
148 );
149 existing.item = Either::Item(item);
150 }
151
152 Update::RemoveItem { at } => {
153 let mut entry_to_remove = None;
154
155 for (nth, ItemRow { room_id: room_id_candidate, position, .. }) in
156 self.items.iter_mut().enumerate()
157 {
158 if room_id != room_id_candidate {
160 continue;
161 }
162
163 if *position == at {
165 debug_assert!(entry_to_remove.is_none(), "Found the same entry twice");
166
167 entry_to_remove = Some(nth);
168 }
169
170 if position.chunk_identifier() == at.chunk_identifier()
172 && position.index() > at.index()
173 {
174 position.decrement_index();
175 }
176 }
177
178 self.items.remove(entry_to_remove.expect("Remove an unknown item"));
179 }
180
181 Update::DetachLastItems { at } => {
182 let indices_to_remove = self
183 .items
184 .iter()
185 .enumerate()
186 .filter_map(
187 |(nth, ItemRow { room_id: room_id_candidate, position, .. })| {
188 (room_id == room_id_candidate
189 && position.chunk_identifier() == at.chunk_identifier()
190 && position.index() >= at.index())
191 .then_some(nth)
192 },
193 )
194 .collect::<Vec<_>>();
195
196 for index_to_remove in indices_to_remove.into_iter().rev() {
197 self.items.remove(index_to_remove);
198 }
199 }
200
201 Update::StartReattachItems | Update::EndReattachItems => { }
202
203 Update::Clear => {
204 self.chunks.retain(|chunk| chunk.room_id != room_id);
205 self.items.retain(|chunk| chunk.room_id != room_id);
206 }
207 }
208 }
209
210 fn insert_chunk(
211 chunks: &mut Vec<ChunkRow>,
212 room_id: &RoomId,
213 previous: Option<ChunkIdentifier>,
214 new: ChunkIdentifier,
215 next: Option<ChunkIdentifier>,
216 ) {
217 if let Some(previous) = previous {
219 let entry_for_previous_chunk = chunks
220 .iter_mut()
221 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
222 room_id == room_id_candidate && *chunk == previous
223 })
224 .expect("Previous chunk should be present");
225
226 entry_for_previous_chunk.next_chunk = Some(new);
228 }
229
230 if let Some(next) = next {
232 let entry_for_next_chunk = chunks
233 .iter_mut()
234 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
235 room_id == room_id_candidate && *chunk == next
236 })
237 .expect("Next chunk should be present");
238
239 entry_for_next_chunk.previous_chunk = Some(new);
241 }
242
243 chunks.push(ChunkRow {
245 room_id: room_id.to_owned(),
246 previous_chunk: previous,
247 chunk: new,
248 next_chunk: next,
249 });
250 }
251
252 fn remove_chunk(
253 chunks: &mut Vec<ChunkRow>,
254 room_id: &RoomId,
255 chunk_to_remove: ChunkIdentifier,
256 ) {
257 let entry_nth_to_remove = chunks
258 .iter()
259 .enumerate()
260 .find_map(|(nth, ChunkRow { room_id: room_id_candidate, chunk, .. })| {
261 (room_id == room_id_candidate && *chunk == chunk_to_remove).then_some(nth)
262 })
263 .expect("Remove an unknown chunk");
264
265 let ChunkRow { room_id, previous_chunk: previous, next_chunk: next, .. } =
266 chunks.remove(entry_nth_to_remove);
267
268 if let Some(previous) = previous {
270 let entry_for_previous_chunk = chunks
271 .iter_mut()
272 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
273 &room_id == room_id_candidate && *chunk == previous
274 })
275 .expect("Previous chunk should be present");
276
277 entry_for_previous_chunk.next_chunk = next;
279 }
280
281 if let Some(next) = next {
283 let entry_for_next_chunk = chunks
284 .iter_mut()
285 .find(|ChunkRow { room_id: room_id_candidate, chunk, .. }| {
286 &room_id == room_id_candidate && *chunk == next
287 })
288 .expect("Next chunk should be present");
289
290 entry_for_next_chunk.previous_chunk = previous;
292 }
293 }
294 }
295}
296
297impl<Item, Gap> RelationalLinkedChunk<Item, Gap>
298where
299 Gap: Clone,
300 Item: Clone,
301{
302 pub fn reload_chunks(&self, room_id: &RoomId) -> Result<Vec<RawChunk<Item, Gap>>, String> {
307 let mut result = Vec::new();
308
309 for chunk_row in self.chunks.iter().filter(|chunk| chunk.room_id == room_id) {
310 let mut items = self
312 .items
313 .iter()
314 .filter(|row| {
315 row.room_id == room_id && row.position.chunk_identifier() == chunk_row.chunk
316 })
317 .peekable();
318
319 let Some(first) = items.peek() else {
321 result.push(RawChunk {
324 content: ChunkContent::Items(Vec::new()),
325 previous: chunk_row.previous_chunk,
326 identifier: chunk_row.chunk,
327 next: chunk_row.next_chunk,
328 });
329 continue;
330 };
331
332 match &first.item {
333 Either::Item(_) => {
334 let mut collected_items = Vec::new();
336 for row in items {
337 match &row.item {
338 Either::Item(item) => {
339 collected_items.push((item.clone(), row.position.index()))
340 }
341 Either::Gap(_) => {
342 return Err(format!(
343 "unexpected gap in items chunk {}",
344 chunk_row.chunk.index()
345 ));
346 }
347 }
348 }
349
350 collected_items.sort_unstable_by_key(|(_item, index)| *index);
352
353 result.push(RawChunk {
354 content: ChunkContent::Items(
355 collected_items.into_iter().map(|(item, _index)| item).collect(),
356 ),
357 previous: chunk_row.previous_chunk,
358 identifier: chunk_row.chunk,
359 next: chunk_row.next_chunk,
360 });
361 }
362
363 Either::Gap(gap) => {
364 assert!(items.next().is_some(), "we just peeked the gap");
365
366 if items.next().is_some() {
368 return Err(format!(
369 "there shouldn't be more than one item row attached in gap chunk {}",
370 chunk_row.chunk.index()
371 ));
372 }
373
374 result.push(RawChunk {
375 content: ChunkContent::Gap(gap.clone()),
376 previous: chunk_row.previous_chunk,
377 identifier: chunk_row.chunk,
378 next: chunk_row.next_chunk,
379 });
380 }
381 }
382 }
383
384 Ok(result)
385 }
386}
387
388impl<Item, Gap> Default for RelationalLinkedChunk<Item, Gap> {
389 fn default() -> Self {
390 Self::new()
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use ruma::room_id;
397
398 use super::{ChunkIdentifier as CId, *};
399 use crate::linked_chunk::LinkedChunkBuilder;
400
401 #[test]
402 fn test_new_items_chunk() {
403 let room_id = room_id!("!r0:matrix.org");
404 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
405
406 relational_linked_chunk.apply_updates(
407 room_id,
408 vec![
409 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
411 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
413 Update::NewItemsChunk { previous: None, new: CId::new(2), next: Some(CId::new(0)) },
415 Update::NewItemsChunk {
417 previous: Some(CId::new(2)),
418 new: CId::new(3),
419 next: Some(CId::new(0)),
420 },
421 ],
422 );
423
424 assert_eq!(
426 relational_linked_chunk.chunks,
427 &[
428 ChunkRow {
429 room_id: room_id.to_owned(),
430 previous_chunk: Some(CId::new(3)),
431 chunk: CId::new(0),
432 next_chunk: Some(CId::new(1))
433 },
434 ChunkRow {
435 room_id: room_id.to_owned(),
436 previous_chunk: Some(CId::new(0)),
437 chunk: CId::new(1),
438 next_chunk: None
439 },
440 ChunkRow {
441 room_id: room_id.to_owned(),
442 previous_chunk: None,
443 chunk: CId::new(2),
444 next_chunk: Some(CId::new(3))
445 },
446 ChunkRow {
447 room_id: room_id.to_owned(),
448 previous_chunk: Some(CId::new(2)),
449 chunk: CId::new(3),
450 next_chunk: Some(CId::new(0))
451 },
452 ],
453 );
454 assert!(relational_linked_chunk.items.is_empty());
456 }
457
458 #[test]
459 fn test_new_gap_chunk() {
460 let room_id = room_id!("!r0:matrix.org");
461 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
462
463 relational_linked_chunk.apply_updates(
464 room_id,
465 vec![
466 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
468 Update::NewGapChunk {
470 previous: Some(CId::new(0)),
471 new: CId::new(1),
472 next: None,
473 gap: (),
474 },
475 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
477 ],
478 );
479
480 assert_eq!(
482 relational_linked_chunk.chunks,
483 &[
484 ChunkRow {
485 room_id: room_id.to_owned(),
486 previous_chunk: None,
487 chunk: CId::new(0),
488 next_chunk: Some(CId::new(1))
489 },
490 ChunkRow {
491 room_id: room_id.to_owned(),
492 previous_chunk: Some(CId::new(0)),
493 chunk: CId::new(1),
494 next_chunk: Some(CId::new(2))
495 },
496 ChunkRow {
497 room_id: room_id.to_owned(),
498 previous_chunk: Some(CId::new(1)),
499 chunk: CId::new(2),
500 next_chunk: None
501 },
502 ],
503 );
504 assert_eq!(
506 relational_linked_chunk.items,
507 &[ItemRow {
508 room_id: room_id.to_owned(),
509 position: Position::new(CId::new(1), 0),
510 item: Either::Gap(())
511 }],
512 );
513 }
514
515 #[test]
516 fn test_remove_chunk() {
517 let room_id = room_id!("!r0:matrix.org");
518 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
519
520 relational_linked_chunk.apply_updates(
521 room_id,
522 vec![
523 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
525 Update::NewGapChunk {
527 previous: Some(CId::new(0)),
528 new: CId::new(1),
529 next: None,
530 gap: (),
531 },
532 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
534 Update::RemoveChunk(CId::new(1)),
536 ],
537 );
538
539 assert_eq!(
541 relational_linked_chunk.chunks,
542 &[
543 ChunkRow {
544 room_id: room_id.to_owned(),
545 previous_chunk: None,
546 chunk: CId::new(0),
547 next_chunk: Some(CId::new(2))
548 },
549 ChunkRow {
550 room_id: room_id.to_owned(),
551 previous_chunk: Some(CId::new(0)),
552 chunk: CId::new(2),
553 next_chunk: None
554 },
555 ],
556 );
557 assert!(relational_linked_chunk.items.is_empty());
559 }
560
561 #[test]
562 fn test_push_items() {
563 let room_id = room_id!("!r0:matrix.org");
564 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
565
566 relational_linked_chunk.apply_updates(
567 room_id,
568 vec![
569 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
571 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
573 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
575 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
577 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
579 ],
580 );
581
582 assert_eq!(
584 relational_linked_chunk.chunks,
585 &[
586 ChunkRow {
587 room_id: room_id.to_owned(),
588 previous_chunk: None,
589 chunk: CId::new(0),
590 next_chunk: Some(CId::new(1))
591 },
592 ChunkRow {
593 room_id: room_id.to_owned(),
594 previous_chunk: Some(CId::new(0)),
595 chunk: CId::new(1),
596 next_chunk: None
597 },
598 ],
599 );
600 assert_eq!(
602 relational_linked_chunk.items,
603 &[
604 ItemRow {
605 room_id: room_id.to_owned(),
606 position: Position::new(CId::new(0), 0),
607 item: Either::Item('a')
608 },
609 ItemRow {
610 room_id: room_id.to_owned(),
611 position: Position::new(CId::new(0), 1),
612 item: Either::Item('b')
613 },
614 ItemRow {
615 room_id: room_id.to_owned(),
616 position: Position::new(CId::new(0), 2),
617 item: Either::Item('c')
618 },
619 ItemRow {
620 room_id: room_id.to_owned(),
621 position: Position::new(CId::new(1), 0),
622 item: Either::Item('x')
623 },
624 ItemRow {
625 room_id: room_id.to_owned(),
626 position: Position::new(CId::new(1), 1),
627 item: Either::Item('y')
628 },
629 ItemRow {
630 room_id: room_id.to_owned(),
631 position: Position::new(CId::new(1), 2),
632 item: Either::Item('z')
633 },
634 ItemRow {
635 room_id: room_id.to_owned(),
636 position: Position::new(CId::new(0), 3),
637 item: Either::Item('d')
638 },
639 ItemRow {
640 room_id: room_id.to_owned(),
641 position: Position::new(CId::new(0), 4),
642 item: Either::Item('e')
643 },
644 ],
645 );
646 }
647
648 #[test]
649 fn test_remove_item() {
650 let room_id = room_id!("!r0:matrix.org");
651 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
652
653 relational_linked_chunk.apply_updates(
654 room_id,
655 vec![
656 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
658 Update::PushItems {
660 at: Position::new(CId::new(0), 0),
661 items: vec!['a', 'b', 'c', 'd', 'e'],
662 },
663 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
665 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
667 ],
668 );
669
670 assert_eq!(
672 relational_linked_chunk.chunks,
673 &[ChunkRow {
674 room_id: room_id.to_owned(),
675 previous_chunk: None,
676 chunk: CId::new(0),
677 next_chunk: None
678 }],
679 );
680 assert_eq!(
682 relational_linked_chunk.items,
683 &[
684 ItemRow {
685 room_id: room_id.to_owned(),
686 position: Position::new(CId::new(0), 0),
687 item: Either::Item('b')
688 },
689 ItemRow {
690 room_id: room_id.to_owned(),
691 position: Position::new(CId::new(0), 1),
692 item: Either::Item('c')
693 },
694 ItemRow {
695 room_id: room_id.to_owned(),
696 position: Position::new(CId::new(0), 2),
697 item: Either::Item('e')
698 },
699 ],
700 );
701 }
702
703 #[test]
704 fn test_detach_last_items() {
705 let room_id = room_id!("!r0:matrix.org");
706 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
707
708 relational_linked_chunk.apply_updates(
709 room_id,
710 vec![
711 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
713 Update::NewItemsChunk { previous: Some(CId::new(0)), new: CId::new(1), next: None },
715 Update::PushItems {
717 at: Position::new(CId::new(0), 0),
718 items: vec!['a', 'b', 'c', 'd', 'e'],
719 },
720 Update::PushItems { at: Position::new(CId::new(1), 0), items: vec!['x', 'y', 'z'] },
722 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
724 ],
725 );
726
727 assert_eq!(
729 relational_linked_chunk.chunks,
730 &[
731 ChunkRow {
732 room_id: room_id.to_owned(),
733 previous_chunk: None,
734 chunk: CId::new(0),
735 next_chunk: Some(CId::new(1))
736 },
737 ChunkRow {
738 room_id: room_id.to_owned(),
739 previous_chunk: Some(CId::new(0)),
740 chunk: CId::new(1),
741 next_chunk: None
742 },
743 ],
744 );
745 assert_eq!(
747 relational_linked_chunk.items,
748 &[
749 ItemRow {
750 room_id: room_id.to_owned(),
751 position: Position::new(CId::new(0), 0),
752 item: Either::Item('a')
753 },
754 ItemRow {
755 room_id: room_id.to_owned(),
756 position: Position::new(CId::new(0), 1),
757 item: Either::Item('b')
758 },
759 ItemRow {
760 room_id: room_id.to_owned(),
761 position: Position::new(CId::new(1), 0),
762 item: Either::Item('x')
763 },
764 ItemRow {
765 room_id: room_id.to_owned(),
766 position: Position::new(CId::new(1), 1),
767 item: Either::Item('y')
768 },
769 ItemRow {
770 room_id: room_id.to_owned(),
771 position: Position::new(CId::new(1), 2),
772 item: Either::Item('z')
773 },
774 ],
775 );
776 }
777
778 #[test]
779 fn test_start_and_end_reattach_items() {
780 let room_id = room_id!("!r0:matrix.org");
781 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
782
783 relational_linked_chunk
784 .apply_updates(room_id, vec![Update::StartReattachItems, Update::EndReattachItems]);
785
786 assert!(relational_linked_chunk.chunks.is_empty());
788 assert!(relational_linked_chunk.items.is_empty());
789 }
790
791 #[test]
792 fn test_clear() {
793 let r0 = room_id!("!r0:matrix.org");
794 let r1 = room_id!("!r1:matrix.org");
795 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
796
797 relational_linked_chunk.apply_updates(
798 r0,
799 vec![
800 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
802 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
804 ],
805 );
806
807 relational_linked_chunk.apply_updates(
808 r1,
809 vec![
810 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
812 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
814 ],
815 );
816
817 assert_eq!(
819 relational_linked_chunk.chunks,
820 &[
821 ChunkRow {
822 room_id: r0.to_owned(),
823 previous_chunk: None,
824 chunk: CId::new(0),
825 next_chunk: None,
826 },
827 ChunkRow {
828 room_id: r1.to_owned(),
829 previous_chunk: None,
830 chunk: CId::new(0),
831 next_chunk: None,
832 }
833 ],
834 );
835
836 assert_eq!(
838 relational_linked_chunk.items,
839 &[
840 ItemRow {
841 room_id: r0.to_owned(),
842 position: Position::new(CId::new(0), 0),
843 item: Either::Item('a')
844 },
845 ItemRow {
846 room_id: r0.to_owned(),
847 position: Position::new(CId::new(0), 1),
848 item: Either::Item('b')
849 },
850 ItemRow {
851 room_id: r0.to_owned(),
852 position: Position::new(CId::new(0), 2),
853 item: Either::Item('c')
854 },
855 ItemRow {
856 room_id: r1.to_owned(),
857 position: Position::new(CId::new(0), 0),
858 item: Either::Item('x')
859 },
860 ],
861 );
862
863 relational_linked_chunk.apply_updates(r0, vec![Update::Clear]);
865
866 assert_eq!(
868 relational_linked_chunk.chunks,
869 &[ChunkRow {
870 room_id: r1.to_owned(),
871 previous_chunk: None,
872 chunk: CId::new(0),
873 next_chunk: None,
874 }],
875 );
876
877 assert_eq!(
878 relational_linked_chunk.items,
879 &[ItemRow {
880 room_id: r1.to_owned(),
881 position: Position::new(CId::new(0), 0),
882 item: Either::Item('x')
883 },],
884 );
885 }
886
887 #[test]
888 fn test_reload_empty_linked_chunk() {
889 let room_id = room_id!("!r0:matrix.org");
890
891 let relational_linked_chunk = RelationalLinkedChunk::<char, char>::new();
893 let result = relational_linked_chunk.reload_chunks(room_id).unwrap();
894 assert!(result.is_empty());
895 }
896
897 #[test]
898 fn test_reload_linked_chunk_with_empty_items() {
899 let room_id = room_id!("!r0:matrix.org");
900
901 let mut relational_linked_chunk = RelationalLinkedChunk::<char, char>::new();
902
903 relational_linked_chunk.apply_updates(
905 room_id,
906 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
907 );
908
909 let raws = relational_linked_chunk.reload_chunks(room_id).unwrap();
911 let lc = LinkedChunkBuilder::<3, _, _>::from_raw_parts(raws)
912 .build()
913 .expect("building succeeds")
914 .expect("this leads to a non-empty linked chunk");
915
916 assert_items_eq!(lc, []);
917 }
918
919 #[test]
920 fn test_rebuild_linked_chunk() {
921 let room_id = room_id!("!r0:matrix.org");
922 let mut relational_linked_chunk = RelationalLinkedChunk::<char, char>::new();
923
924 relational_linked_chunk.apply_updates(
925 room_id,
926 vec![
927 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
929 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
931 Update::NewGapChunk {
933 previous: Some(CId::new(0)),
934 new: CId::new(1),
935 next: None,
936 gap: 'g',
937 },
938 Update::NewItemsChunk { previous: Some(CId::new(1)), new: CId::new(2), next: None },
940 Update::PushItems { at: Position::new(CId::new(2), 0), items: vec!['d', 'e', 'f'] },
942 ],
943 );
944
945 let raws = relational_linked_chunk.reload_chunks(room_id).unwrap();
946 let lc = LinkedChunkBuilder::<3, _, _>::from_raw_parts(raws)
947 .build()
948 .expect("building succeeds")
949 .expect("this leads to a non-empty linked chunk");
950
951 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
953 }
954
955 #[test]
956 fn test_replace_item() {
957 let room_id = room_id!("!r0:matrix.org");
958 let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new();
959
960 relational_linked_chunk.apply_updates(
961 room_id,
962 vec![
963 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
965 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['a', 'b', 'c'] },
967 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
969 ],
970 );
971
972 assert_eq!(
974 relational_linked_chunk.chunks,
975 &[ChunkRow {
976 room_id: room_id.to_owned(),
977 previous_chunk: None,
978 chunk: CId::new(0),
979 next_chunk: None,
980 },],
981 );
982
983 assert_eq!(
985 relational_linked_chunk.items,
986 &[
987 ItemRow {
988 room_id: room_id.to_owned(),
989 position: Position::new(CId::new(0), 0),
990 item: Either::Item('a')
991 },
992 ItemRow {
993 room_id: room_id.to_owned(),
994 position: Position::new(CId::new(0), 1),
995 item: Either::Item('B')
996 },
997 ItemRow {
998 room_id: room_id.to_owned(),
999 position: Position::new(CId::new(0), 2),
1000 item: Either::Item('c')
1001 },
1002 ],
1003 );
1004 }
1005}