mod common;
use common::*;
#[test]
fn synced_delete_can_remove_entire_sync_clips() {
let mut stack = Stack::default();
stack
.children
.push(Track::new(TrackKind::Video, Some("v".to_string())));
stack
.children
.push(Track::new(TrackKind::Audio, Some("a".to_string())));
let result = insert_with_audio(
&mut stack,
0,
0.0,
clip(3.0, Some("primary")),
vec![audio_clip(3.0, "file:///a1.wav", None)],
)
.unwrap();
let removed = stack.delete_item("primary", true, true);
assert_eq!(removed.len(), 2);
assert!(stack.children[0]
.items
.iter()
.all(|item| matches!(item, Item::Gap(_))));
assert!(stack.children[result.audio_clips[0].1]
.items
.iter()
.all(|item| matches!(item, Item::Gap(_))));
let gap_ids: Vec<_> = stack
.children
.iter()
.flat_map(|track| track.items.iter())
.filter_map(|item| item.get_id())
.collect();
let unique: std::collections::HashSet<_> = gap_ids.iter().collect();
assert_eq!(gap_ids.len(), unique.len());
}
#[test]
fn synced_delete_keeps_touched_tracks_without_remaining_clips() {
let mut stack = Stack::default();
stack
.children
.push(Track::new(TrackKind::Video, Some("v".to_string())));
insert_with_audio(
&mut stack,
0,
0.0,
clip(3.0, Some("primary")),
vec![audio_clip(3.0, "file:///a1.wav", None)],
)
.unwrap();
let removed = stack.delete_item("primary", true, true);
assert_eq!(removed.len(), 2);
assert_eq!(stack.children.len(), 2);
assert!(stack
.children
.iter()
.all(|track| track.items.iter().all(|item| matches!(item, Item::Gap(_)))));
}
#[test]
fn synced_delete_without_gap_collapses_sync_clips() {
let mut stack = Stack::default();
stack
.children
.push(Track::new(TrackKind::Video, Some("v".to_string())));
stack
.children
.push(Track::new(TrackKind::Audio, Some("a".to_string())));
insert_with_audio(
&mut stack,
0,
0.0,
clip(3.0, Some("primary")),
vec![audio_clip(3.0, "file:///a1.wav", None)],
)
.unwrap();
let removed = stack.delete_item("primary", false, true);
assert_eq!(removed.len(), 2);
assert!(stack.children.iter().all(|track| track.items.is_empty()));
}
#[test]
fn delete_clip_a_replace_with_gap_preserves_synced_partner_spans() {
let mut stack = stack_v1_clip_a_clip_b_a1();
let v1 = track_index_by_id(&stack, "v1");
let a1 = track_index_by_id(&stack, "a1");
let removed = stack.delete_item("clip-a", true, true);
assert_eq!(removed.len(), 1);
assert!(stack.get_item("clip-a").is_none());
assert!(stack.get_item("clip-b-video").is_some());
assert!(stack.get_item("clip-b-audio").is_some());
let video = &stack.children[v1];
assert_eq!(video.items.len(), 2);
assert!(matches!(video.items[0], Item::Gap(_)));
assert_item_span(video, 0, 0.0, 10.0);
assert_item_span(video, 1, 10.0, 10.0);
let audio = &stack.children[a1];
assert_eq!(audio.items.len(), 2);
assert!(matches!(audio.items[0], Item::Gap(_)));
assert_item_span(audio, 0, 0.0, 10.0);
assert_item_span(audio, 1, 10.0, 10.0);
assert_eq!(
sync_clips_id(&video.items[1]),
sync_clips_id(&audio.items[1])
);
}
#[test]
fn delete_clip_a_collapse_pulls_synced_partner_left() {
let mut stack = stack_v1_clip_a_clip_b_a1();
let v1 = track_index_by_id(&stack, "v1");
let a1 = track_index_by_id(&stack, "a1");
let removed = stack.delete_item("clip-a", false, true);
assert_eq!(removed.len(), 2);
assert!(stack.get_item("clip-a").is_none());
assert!(stack.get_item("clip-b-video").is_some());
assert!(stack.get_item("clip-b-audio").is_some());
let video = &stack.children[v1];
assert_eq!(video.items.len(), 1);
assert_item_span(video, 0, 0.0, 10.0);
let audio = &stack.children[a1];
assert_eq!(audio.items.len(), 1);
assert_item_span(audio, 0, 0.0, 10.0);
assert_eq!(
sync_clips_id(&video.items[0]),
sync_clips_id(&audio.items[0])
);
}
#[test]
fn delete_synced_audio_collapse_removes_video_partner_and_moves_following_clip() {
let mut stack = stack_v1_c1_c2_a1();
let v1 = track_index_by_id(&stack, "v1");
let a1 = track_index_by_id(&stack, "a1");
let removed = stack.delete_item("c1a", false, true);
assert_eq!(removed.len(), 2);
assert!(stack.get_item("c1a").is_none());
assert!(stack.get_item("c1").is_none());
assert!(stack.get_item("c2").is_some());
let video = &stack.children[v1];
assert_eq!(video.items.len(), 1);
assert_item_span(video, 0, 0.0, 4.0);
assert_eq!(video.items[0].get_id().as_deref(), Some("c2"));
assert!(stack.children[a1].items.is_empty());
}
#[test]
fn delete_unsynced_item_only_removes_selected_item() {
let mut video = Track::new(TrackKind::Video, Some("v".to_string()));
video.items.push(Item::Clip(clip(3.0, Some("primary"))));
let mut audio = Track::new(TrackKind::Audio, Some("a".to_string()));
audio
.items
.push(Item::Clip(clip(3.0, Some("unlinked-audio"))));
let mut stack = Stack::default();
stack.children.push(video);
stack.children.push(audio);
let removed = stack.delete_item("primary", true, true);
assert_eq!(removed.len(), 1);
assert!(stack.get_item("primary").is_none());
assert!(stack.get_item("unlinked-audio").is_some());
}
#[test]
fn delete_unsynced_item_without_gap_pulls_later_synced_assets() {
let mut video = Track::new(TrackKind::Video, Some("v".to_string()));
video.items.push(Item::Clip(clip(1.0, Some("unlinked"))));
let audio = Track::new(TrackKind::Audio, Some("a".to_string()));
let mut stack = Stack::default();
stack.children.push(video);
stack.children.push(audio);
let result = insert_with_audio(
&mut stack,
0,
1.0,
clip(2.0, Some("linked-video")),
vec![audio_clip(2.0, "file:///linked-audio.wav", None)],
)
.unwrap();
let audio_id = result.audio_clips[0].0.clone();
let removed = stack.delete_item("unlinked", false, true);
assert_eq!(removed.len(), 2);
let (video_track_index, video_item_index, _) = stack.get_item("linked-video").unwrap();
let (audio_track_index, audio_item_index, _) = stack.get_item(&audio_id).unwrap();
assert_eq!(
stack.children[video_track_index].start_time_of_item(video_item_index),
0.0
);
assert_eq!(
stack.children[audio_track_index].start_time_of_item(audio_item_index),
0.0
);
}
#[test]
fn delete_track_keeps_the_other_channels_of_its_columns() {
let mut stack = Stack::default();
let audio = Track::new(TrackKind::Audio, Some("a".to_string()));
let video = Track::new(TrackKind::Video, Some("v".to_string()));
stack.children.push(audio);
stack.children.push(video);
let result = insert_with_audio(
&mut stack,
1,
0.0,
clip(3.0, Some("primary")),
vec![audio_clip(3.0, "file:///a1.wav", None)],
)
.unwrap();
let audio_id = result.audio_clips[0].0.clone();
let removed = stack.delete_track("v").unwrap();
assert_eq!(removed.get_id().as_deref(), Some("v"));
assert_eq!(stack.children.len(), 1);
assert!(stack
.children
.iter()
.any(|track| track.get_id().as_deref() == Some("a")));
let (_, _, audio_item) = stack.get_item(&audio_id).expect("audio stays");
assert_eq!(audio_item.duration(), 3.0);
assert_eq!(sync_clips_id(audio_item), None);
}
fn stack_with_synced_primary() -> (Stack, String) {
let mut stack = Stack::default();
stack
.children
.push(Track::new(TrackKind::Video, Some("v".to_string())));
stack
.children
.push(Track::new(TrackKind::Audio, Some("a".to_string())));
let result = insert_with_audio(
&mut stack,
0,
0.0,
clip(3.0, Some("primary")),
vec![audio_clip(3.0, "file:///a1.wav", None)],
)
.unwrap();
let audio_id = result.audio_clips[0].0.clone();
(stack, audio_id)
}
#[test]
fn delete_without_sync_clips_keeps_partner() {
let (mut stack, audio_id) = stack_with_synced_primary();
let removed = stack.delete_item("primary", true, false);
assert_eq!(removed.len(), 1);
assert_eq!(removed[0].1.get_id().as_deref(), Some("primary"));
assert!(stack.get_item("primary").is_none());
let (ti, ii, _) = stack.get_item(&audio_id).unwrap();
assert_eq!(stack.children[ti].start_time_of_item(ii), 0.0);
assert!(stack.children[0]
.items
.iter()
.all(|item| matches!(item, Item::Gap(_))));
}
#[test]
fn delete_without_sync_clips_and_without_gap_is_refused() {
let (mut stack, audio_id) = stack_with_synced_primary();
let removed = stack.delete_item("primary", false, false);
assert!(removed.is_empty());
assert!(stack.get_item("primary").is_some());
assert!(stack.get_item(&audio_id).is_some());
}