use crate::diagrams::sequence::compiler;
use crate::mermaid::sequence::parse_sequence;
use crate::timeline::sequence::layout::{
EVENT_GAP, HEADER_HEIGHT, LABEL_PADDING, RowLayout, SELF_MSG_HEIGHT, SequenceLayout, layout,
};
fn layout_input(input: &str) -> SequenceLayout {
let result = parse_sequence(input).unwrap();
let model = compiler::compile(&result.statements).unwrap();
layout(&model)
}
#[test]
fn layout_participants_ordered_left_to_right() {
let layout = layout_input("sequenceDiagram\nparticipant A\nparticipant B\nparticipant C");
assert_eq!(layout.participants.len(), 3);
assert!(layout.participants[0].center_x < layout.participants[1].center_x);
assert!(layout.participants[1].center_x < layout.participants[2].center_x);
}
#[test]
fn layout_participant_gap_accommodates_labels() {
let layout = layout_input(
"sequenceDiagram\nparticipant A\nparticipant B\nA->>B: a very long message label here",
);
let gap = layout.participants[1].center_x - layout.participants[0].center_x;
assert!(gap >= "a very long message label here".len() + LABEL_PADDING);
}
#[test]
fn layout_y_advances_monotonically() {
let layout = layout_input(
"sequenceDiagram\nparticipant A\nparticipant B\nA->>B: first\nB->>A: second\nA->>B: third",
);
let ys: Vec<usize> = layout
.rows
.iter()
.map(|r| match r {
RowLayout::Message { y, .. } => *y,
RowLayout::Note { y, .. } => *y,
})
.collect();
assert!(ys.windows(2).all(|w| w[0] < w[1]));
}
#[test]
fn layout_self_message_takes_more_rows() {
let layout =
layout_input("sequenceDiagram\nparticipant A\nparticipant B\nA->>A: self\nA->>B: normal");
assert_eq!(layout.rows.len(), 2);
let y0 = match &layout.rows[0] {
RowLayout::Message { y, .. } => *y,
_ => panic!("expected message"),
};
let y1 = match &layout.rows[1] {
RowLayout::Message { y, .. } => *y,
_ => panic!("expected message"),
};
assert!(y1 - y0 >= SELF_MSG_HEIGHT);
}
#[test]
fn layout_note_has_height() {
let layout = layout_input(
"sequenceDiagram\nparticipant A\nparticipant B\nNote over A: note\nA->>B: msg",
);
assert_eq!(layout.rows.len(), 2);
let note_y = match &layout.rows[0] {
RowLayout::Note { y, .. } => *y,
_ => panic!("expected note"),
};
let msg_y = match &layout.rows[1] {
RowLayout::Message { y, .. } => *y,
_ => panic!("expected message"),
};
assert!(msg_y >= note_y + 3);
}
#[test]
fn layout_header_height() {
let layout = layout_input("sequenceDiagram\nparticipant A\nA->>A: hi");
let first_y = match &layout.rows[0] {
RowLayout::Message { y, .. } => *y,
_ => panic!("expected message"),
};
assert_eq!(first_y, HEADER_HEIGHT + EVENT_GAP);
}
#[test]
fn layout_box_width_matches_label() {
let layout = layout_input("sequenceDiagram\nparticipant A as Alice");
assert_eq!(layout.participants[0].box_width, "Alice".len() + 4);
}
#[test]
fn layout_create_participant_moves_header_to_creation_point() {
let layout = layout_input(
"\
sequenceDiagram
participant A
create participant B
A->>B: Create
B-->>A: Ready",
);
assert!(layout.participants[1].box_y > layout.participants[0].box_y);
assert_eq!(
layout.participants[1].lifeline_start_y,
layout.participants[1].box_y + HEADER_HEIGHT
);
match &layout.rows[0] {
RowLayout::Message {
y,
from_x,
to_x,
from_idx,
to_idx,
..
} => {
assert_eq!(*from_idx, 0);
assert_eq!(*to_idx, 1);
assert_eq!(*to_x, layout.participants[1].box_x);
assert_eq!(*y, layout.participants[1].box_y + 1);
assert!(from_x < to_x);
}
_ => panic!("expected create message row"),
}
}
#[test]
fn layout_destroy_participant_truncates_lifeline() {
let layout = layout_input(
"\
sequenceDiagram
participant A
participant B
destroy B
A->>B: Goodbye",
);
let destroy_y = layout.participants[1]
.destroy_y
.expect("destroy marker should be recorded");
assert_eq!(layout.participants[1].lifeline_end_y, destroy_y);
assert!(layout.participants[1].lifeline_end_y < layout.height);
}
#[test]
fn layout_title_offsets_headers() {
let layout =
layout_input("sequenceDiagram\ntitle Authentication Flow\nparticipant A\nA->>A: hi");
assert_eq!(
layout.title.as_ref().map(|title| title.text.as_str()),
Some("Authentication Flow")
);
assert!(layout.participants[0].box_y > 0);
}
#[test]
fn layout_tracks_interaction_blocks() {
let layout = layout_input(
"\
sequenceDiagram
participant A
participant B
alt available
A->>B: Request
else busy
B->>A: Retry later
end",
);
assert_eq!(layout.blocks.len(), 1);
let block = &layout.blocks[0];
assert!(block.left_x < block.right_x);
assert_eq!(block.dividers.len(), 1);
assert!(block.top_y < block.dividers[0].y);
assert!(block.dividers[0].y < block.bottom_y);
}
#[test]
fn layout_nested_blocks_have_increasing_depth() {
let layout = layout_input(
"\
sequenceDiagram
participant A
participant B
loop outer
alt ready
A->>B: Request
else later
B->>A: Retry
end
end",
);
assert_eq!(layout.blocks.len(), 2);
assert_eq!(layout.blocks[0].depth, 0);
assert_eq!(layout.blocks[1].depth, 1);
}