extern crate alloc;
use alloc::vec::Vec;
use crate::metis::{
Anchor, Composer, DotSet, Extent, Metatheses, Metathesis, Rhapsody, Scholion, Verge,
};
use super::{Moves, Text, clock, d, locus_at, move_to, type_after};
#[test]
fn test_a_move_into_an_unplaced_birth_cycle_is_not_refused() {
let clk = clock(1);
let target = d(1, 1);
let child = d(1, 2);
let first_unplaced = d(2, 1);
let second_unplaced = d(2, 2);
let mut text = Rhapsody::new();
for (dot, anchor) in [
(target, Anchor::Origin),
(child, Anchor::After(target.into())),
(first_unplaced, Anchor::After(second_unplaced.into())),
(second_unplaced, Anchor::After(first_unplaced.into())),
] {
assert!(text.weave(dot, locus_at(&clk, anchor)));
}
assert_eq!(text.order(), [target, child]);
let mut moves = Metatheses::new();
let witness = d(3, 1);
let destination = locus_at(&clk, Anchor::After(first_unplaced.into()));
assert!(moves.insert(
witness,
Metathesis {
target: target.into(),
to: destination,
},
));
let recension = text.recension(&moves);
assert!(recension.refused().is_empty());
assert!(recension.pending().is_empty());
assert_eq!(recension.locus(target), Some(destination));
assert!(recension.order().is_empty());
}
#[test]
fn test_the_exhibit_flips_a_concurrent_edit_follows_the_move() {
let mut text_a: Text = Composer::new(1);
let mut moves_a: Moves = Composer::new(1);
let mut text_b: Text = Composer::new(2);
let mut moves_b: Moves = Composer::new(2);
let clk_a = clock(1);
let clk_b = clock(2);
let first = type_after(&mut text_a, &clk_a, None);
let second = type_after(&mut text_a, &clk_a, Some(first.into()));
let _ = text_b.absorb(1, &text_a.owed_to(text_b.state().context()));
let (_, move_delta) = move_to(
&mut moves_a,
second.into(),
locus_at(&clk_a, Anchor::Before(first.into())),
);
let edit = type_after(&mut text_b, &clk_b, Some(second.into()));
let _ = text_a.absorb(2, &text_b.owed_to(text_a.state().context()));
let _ = text_b.absorb(1, &text_a.owed_to(text_b.state().context()));
let _ = moves_b.absorb(1, &move_delta);
assert_eq!(text_a.state(), text_b.state());
assert_eq!(moves_a.state().store(), moves_b.state().store());
for (text, moves) in [(&text_a, &moves_a), (&text_b, &moves_b)] {
let recension = text.state().store().recension(moves.state().store());
assert_eq!(recension.order(), [second, edit, first]);
assert!(recension.refused().is_empty());
assert!(recension.pending().is_empty());
}
assert_eq!(text_a.state().store().order(), [first, second, edit]);
}
#[test]
fn test_concurrent_moves_of_one_element_resolve_by_rank() {
let mut text: Text = Composer::new(1);
let mut moves_a: Moves = Composer::new(1);
let mut moves_b: Moves = Composer::new(2);
let clk_a = clock(1);
let clk_b = clock(2);
let a = type_after(&mut text, &clk_a, None);
let b = type_after(&mut text, &clk_a, Some(a.into()));
let c = type_after(&mut text, &clk_a, Some(b.into()));
let (_, from_a) = move_to(
&mut moves_a,
c.into(),
locus_at(&clk_a, Anchor::Before(a.into())),
);
clk_b.observe(clk_a.now(0u16)); let (_, from_b) = move_to(
&mut moves_b,
c.into(),
locus_at(&clk_b, Anchor::After(a.into())),
);
let _ = moves_a.absorb(2, &from_b);
let _ = moves_b.absorb(1, &from_a);
assert_eq!(moves_a.state().store(), moves_b.state().store());
for moves in [&moves_a, &moves_b] {
let recension = text.state().store().recension(moves.state().store());
assert_eq!(recension.order(), [a, c, b], "the later rank wins");
assert!(recension.refused().is_empty());
}
}
#[test]
fn test_a_move_cycle_is_refused_deterministically() {
let mut text: Text = Composer::new(1);
let mut moves_a: Moves = Composer::new(1);
let mut moves_b: Moves = Composer::new(2);
let clk_a = clock(1);
let clk_b = clock(2);
let weave_at_origin = |text: &mut Text, rank| {
let (dot, _) = text.compose(|dot| {
let mut delta = crate::metis::Rhapsody::new();
assert!(delta.weave(
dot,
crate::metis::Locus {
anchor: Anchor::Origin,
rank,
}
));
(delta, DotSet::new())
});
dot
};
let a = weave_at_origin(&mut text, clk_a.now(0u16));
let b = weave_at_origin(&mut text, clk_a.now(0u16));
assert_eq!(text.state().store().order(), [b, a]);
let (_, from_a) = move_to(
&mut moves_a,
a.into(),
locus_at(&clk_a, Anchor::After(b.into())),
);
clk_b.observe(clk_a.now(0u16));
let (second_move, from_b) = move_to(
&mut moves_b,
b.into(),
locus_at(&clk_b, Anchor::After(a.into())),
);
let _ = moves_a.absorb(2, &from_b);
let _ = moves_b.absorb(1, &from_a);
for moves in [&moves_a, &moves_b] {
let recension = text.state().store().recension(moves.state().store());
assert_eq!(recension.order(), [b, a]);
assert_eq!(recension.refused(), [second_move]);
assert_eq!(
recension.locus(a).map(|l| l.anchor),
Some(Anchor::After(b.into()))
);
assert_eq!(recension.visible_len(), 2);
}
}
#[test]
fn test_a_self_move_is_refused() {
let mut text: Text = Composer::new(1);
let mut moves: Moves = Composer::new(1);
let clk = clock(1);
let a = type_after(&mut text, &clk, None);
let b = type_after(&mut text, &clk, Some(a.into()));
let c = type_after(&mut text, &clk, Some(b.into()));
let (testimony, _) = move_to(
&mut moves,
c.into(),
locus_at(&clk, Anchor::After(c.into())),
);
let recension = text.state().store().recension(moves.state().store());
assert_eq!(recension.refused(), [testimony], "the self-move is refused");
assert_eq!(recension.order(), [a, b, c]);
assert!(recension.is_visible(c));
}
#[test]
fn test_a_move_ranked_below_its_anchor_birth_refuses_the_move() {
let clk = clock(1);
let r_target = clk.now(0u16);
let r_move = clk.now(0u16);
let r_anchor = clk.now(0u16);
let mut text: Text = Composer::new(1);
let (target, _) = text.compose(|dot| {
let mut delta = crate::metis::Rhapsody::new();
assert!(delta.weave(
dot,
crate::metis::Locus {
anchor: Anchor::Origin,
rank: r_target,
}
));
(delta, DotSet::new())
});
let (anchor, _) = text.compose(|dot| {
let mut delta = crate::metis::Rhapsody::new();
assert!(delta.weave(
dot,
crate::metis::Locus {
anchor: Anchor::After(target.into()),
rank: r_anchor,
}
));
(delta, DotSet::new())
});
assert_eq!(text.state().store().order(), [target, anchor]);
let mut moves: Moves = Composer::new(1);
let (m, _) = moves.compose(|dot| {
(
Metatheses::singleton(
dot,
crate::metis::Metathesis {
target: target.into(),
to: crate::metis::Locus {
anchor: Anchor::After(anchor.into()),
rank: r_move,
},
},
),
DotSet::new(),
)
});
let recension = text.state().store().recension(moves.state().store());
assert_eq!(recension.refused(), [m], "the move is refused, not a birth");
assert_eq!(
recension.order(),
[target, anchor],
"both births stay rendered"
);
assert!(recension.is_visible(target));
assert!(recension.is_visible(anchor));
}
#[test]
fn test_undoing_a_move_by_retract_restores_the_birth_order() {
let mut text: Text = Composer::new(1);
let mut moves: Moves = Composer::new(1);
let clk = clock(1);
let a = type_after(&mut text, &clk, None);
let b = type_after(&mut text, &clk, Some(a.into()));
let (testimony, _) = move_to(
&mut moves,
b.into(),
locus_at(&clk, Anchor::Before(a.into())),
);
let store = text.state().store();
assert_eq!(store.recension(moves.state().store()).order(), [b, a]);
let mut undo = DotSet::new();
assert!(undo.insert(testimony));
let _ = moves.retract(undo);
assert_eq!(store.recension(moves.state().store()).order(), [a, b]);
assert!(moves.state().store().is_empty());
}
#[test]
fn test_a_re_move_supersedes_tidily() {
let mut text: Text = Composer::new(1);
let mut moves: Moves = Composer::new(1);
let clk = clock(1);
let a = type_after(&mut text, &clk, None);
let b = type_after(&mut text, &clk, Some(a.into()));
let c = type_after(&mut text, &clk, Some(b.into()));
let (_, _) = move_to(
&mut moves,
c.into(),
locus_at(&clk, Anchor::Before(a.into())),
);
let displaced = moves.state().store().moves_of(c.into());
let to = locus_at(&clk, Anchor::After(a.into()));
let (_, _) = moves.compose_super(
|dot| {
Metatheses::singleton(
dot,
crate::metis::Metathesis {
target: c.into(),
to,
},
)
},
|_| displaced.clone(),
);
assert_eq!(moves.state().store().len(), 1, "one surviving testimony");
let recension = text.state().store().recension(moves.state().store());
assert_eq!(recension.order(), [a, c, b]);
}
#[test]
fn test_marks_follow_moved_content() {
let mut text: Text = Composer::new(1);
let mut moves: Moves = Composer::new(1);
let clk = clock(1);
let a = type_after(&mut text, &clk, None);
let b = type_after(&mut text, &clk, Some(a.into()));
let c = type_after(&mut text, &clk, Some(b.into()));
let mark = Scholion {
start: Verge::Before(b.into()),
end: Verge::After(b.into()),
tag: "comment",
};
let (_, _) = move_to(
&mut moves,
b.into(),
locus_at(&clk, Anchor::Before(a.into())),
);
let recension = text.state().store().recension(moves.state().store());
assert_eq!(recension.order(), [b, c, a]);
assert_eq!(
recension.extent(mark.start, mark.end),
Extent::Covered([b].into()),
);
assert_eq!(
text.state().store().extent(mark.start, mark.end),
Extent::Covered([b].into()),
);
}
#[test]
fn test_a_pending_move_surfaces_and_resolves() {
let mut text_a: Text = Composer::new(1);
let mut moves_a: Moves = Composer::new(1);
let mut text_b: Text = Composer::new(2);
let mut moves_b: Moves = Composer::new(2);
let clk = clock(1);
let a = type_after(&mut text_a, &clk, None);
let b = type_after(&mut text_a, &clk, Some(a.into()));
let (testimony, move_delta) = move_to(
&mut moves_a,
b.into(),
locus_at(&clk, Anchor::Before(a.into())),
);
let _ = moves_b.absorb(1, &move_delta);
let empty = text_b.state().store().recension(moves_b.state().store());
assert_eq!(empty.pending(), [testimony]);
assert!(empty.order().is_empty());
let _ = text_b.absorb(1, &text_a.owed_to(text_b.state().context()));
let resolved = text_b.state().store().recension(moves_b.state().store());
assert!(resolved.pending().is_empty());
assert_eq!(resolved.order(), [b, a]);
}
#[test]
fn test_moving_a_tombstone_carries_its_subtree() {
let mut text: Text = Composer::new(1);
let mut moves: Moves = Composer::new(1);
let clk = clock(1);
let a = type_after(&mut text, &clk, None);
let b = type_after(&mut text, &clk, Some(a.into()));
let tail = type_after(&mut text, &clk, Some(b.into()));
let mut gone = DotSet::new();
assert!(gone.insert(b));
let _ = text.retract(gone);
let (_, _) = move_to(
&mut moves,
b.into(),
locus_at(&clk, Anchor::Before(a.into())),
);
let recension = text.state().store().recension(moves.state().store());
assert!(!recension.is_visible(b));
assert_eq!(recension.order(), [tail, a]);
}
#[test]
fn test_recension_of_an_empty_record_is_the_order() {
let mut text: Text = Composer::new(1);
let clk = clock(1);
let mut typed = Vec::new();
let mut caret = None;
for _ in 0..5 {
let dot = type_after(&mut text, &clk, caret);
typed.push(dot);
caret = Some(dot.into());
}
let store = text.state().store();
assert_eq!(
store.order(),
typed,
"the fixture is a plain forward sequence"
);
let recension = store.recension(&Metatheses::new());
assert_eq!(recension.order(), store.order());
assert!(recension.refused().is_empty());
assert!(recension.pending().is_empty());
}