use super::*;
use crate::canvas::undo::UndoStack;
use crate::fixture_editor::clipboard::fixtures_from_clipboard;
use egui::pos2;
fn transact(patch: &mut Patch, state: &mut EditorState, transaction: EditorTransaction) {
transact_with(patch, state, &Collections::default(), transaction);
}
fn transact_with(
patch: &mut Patch,
state: &mut EditorState,
collections: &Collections,
transaction: EditorTransaction,
) {
let mut undo = UndoStack::default();
process_transaction(
&mut PatchEdit::new(patch, &mut undo),
state,
collections,
transaction,
);
}
#[test]
fn a_transaction_is_undone_in_one_step() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let mut undo = UndoStack::default();
let collections = Collections::default();
let mut edit = PatchEdit::new(&mut patch, &mut undo);
let add = || EditorTransaction::AddFixture(LayoutPosition::default(), None);
process_transaction(&mut edit, &mut state, &collections, add());
process_transaction(&mut edit, &mut state, &collections, add());
assert_eq!(edit.value.topology.fixtures.len(), 2);
process_transaction(&mut edit, &mut state, &collections, EditorTransaction::Undo);
assert_eq!(edit.value.topology.fixtures.len(), 1);
assert!(
state.selected_fixtures.is_empty(),
"the fixture the undo took away is no longer selected"
);
process_transaction(&mut edit, &mut state, &collections, EditorTransaction::Redo);
assert_eq!(edit.value.topology.fixtures.len(), 2);
}
#[test]
fn a_transaction_that_changes_nothing_leaves_no_step() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let mut undo = UndoStack::default();
let mut edit = PatchEdit::new(&mut patch, &mut undo);
process_transaction(
&mut edit,
&mut state,
&Collections::default(),
EditorTransaction::CopySelected,
);
assert!(!edit.can_undo());
}
fn wide_collections() -> (
Collections,
gled_model::asset_id::AssetId<gled_model::patching::definition::FixtureDefinition>,
) {
use gled_model::patching::definition::{EmittedColor, FixtureDefinition, Mode, Pixel};
let definition = FixtureDefinition {
size_m: [1.0, 0.05, 0.05],
pixels: (0..100)
.map(|i| Pixel {
name: format!("pixel-{i}"),
model: 0,
position_m: [i as f32 * 0.01, 0.0, 0.0],
rotation: 0.0,
channels: vec![EmittedColor::Red, EmittedColor::Green, EmittedColor::Blue],
})
.collect(),
modes: vec![Mode::individual(100)],
..Default::default()
};
let asset = gled_model::asset::Asset {
id: gled_model::asset_id::AssetId::default(),
path: vec!["wide".to_string()],
data: definition,
};
let id = asset.id;
let mut collection = gled_model::collection::Collection::default();
collection.set_asset(asset);
let mut collections = Collections::default();
collections.insert::<FixtureDefinition>(collection);
(collections, id)
}
#[test]
fn a_added_fixture_starts_past_a_chain_that_overflowed() {
let (collections, definition) = wide_collections();
let mut patch = Patch::default();
let mut state = EditorState::default();
let mut head = Fixture::new("head");
head.definition = Some(definition);
let head = patch.add_fixture(head, LayoutPosition::default());
let mut tail = Fixture::new("tail");
tail.definition = Some(definition);
let tail = patch.add_fixture(tail, LayoutPosition::default());
patch.topology.make_successor(head, tail);
assert_eq!(patch.addresses(&collections)[&tail].into_pair(), (1, 0));
transact_with(
&mut patch,
&mut state,
&collections,
EditorTransaction::AddFixture(LayoutPosition::default(), Some(definition)),
);
let added = *state
.selected_fixtures
.iter()
.next()
.expect("the added fixture is selected");
assert_eq!(patch.topology.ports[&added].universe, 2);
}
#[test]
fn a_detached_chain_starts_past_the_one_it_left() {
let (collections, definition) = wide_collections();
let mut patch = Patch::default();
let mut state = EditorState::default();
let ids: Vec<FixtureId> = ["a", "b", "c"]
.iter()
.map(|name| {
let mut fixture = Fixture::new(*name);
fixture.definition = Some(definition);
patch.add_fixture(fixture, LayoutPosition::default())
})
.collect();
patch.topology.make_successor(ids[0], ids[1]);
patch.topology.make_successor(ids[1], ids[2]);
transact_with(
&mut patch,
&mut state,
&collections,
EditorTransaction::MakeLeaf(ids[1]),
);
assert_eq!(patch.addresses(&collections)[&ids[1]].into_pair(), (1, 0));
assert_eq!(
patch.topology.ports[&ids[2]].universe, 2,
"the detached chain clears the one it left"
);
}
#[test]
fn a_copy_from_the_menu_reaches_the_system_clipboard() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let id = patch.add_fixture(Fixture::new("bar"), LayoutPosition::default());
state.select_single(id);
for transaction in [
EditorTransaction::CopySelected,
EditorTransaction::CutSelected,
] {
let ctx = egui::Context::default();
let mut output = ctx.run_ui(Default::default(), |ui| {
copy_to_system_clipboard(ui.ctx(), &patch, &state, &transaction);
});
output.textures_delta.clear();
let copied = output
.platform_output
.commands
.iter()
.find_map(|command| match command {
egui::OutputCommand::CopyText(text) => fixtures_from_clipboard(text),
_ => None,
})
.expect("the fixtures are on the system clipboard");
assert_eq!(copied.gled_fixtures[0].0.name, "bar");
}
}
#[test]
fn pasting_offsets_the_copies_and_selects_them() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let id = patch.add_fixture(Fixture::new("bar"), LayoutPosition::at(pos2(10.0, 20.0)));
state.select_single(id);
transact(&mut patch, &mut state, EditorTransaction::CopySelected);
transact(&mut patch, &mut state, EditorTransaction::Paste);
assert_eq!(patch.topology.fixtures.len(), 2);
assert_eq!(state.selected_fixtures.len(), 1);
let pasted = *state
.selected_fixtures
.iter()
.next()
.expect("the pasted fixture is selected");
assert_ne!(pasted, id);
assert_eq!(
patch.topology.default_layout.members[&pasted].pos,
pos2(30.0, 40.0)
);
}
#[test]
fn pasting_keeps_the_wiring_inside_the_selection() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let head = patch.add_fixture(Fixture::new("head"), LayoutPosition::default());
let tail = patch.add_fixture(Fixture::new("tail"), LayoutPosition::default());
let outside = patch.add_fixture(Fixture::new("outside"), LayoutPosition::default());
patch.topology.make_successor(head, tail);
patch.topology.make_successor(tail, outside);
state.selected_fixtures = [head, tail].into_iter().collect();
transact(&mut patch, &mut state, EditorTransaction::CopySelected);
transact(&mut patch, &mut state, EditorTransaction::Paste);
let pasted: Vec<FixtureId> = state.selected_fixtures.iter().copied().collect();
assert_eq!(pasted.len(), 2);
let joined = pasted
.iter()
.filter_map(|id| patch.topology.successor(*id))
.filter(|successor| pasted.contains(successor))
.count();
assert_eq!(joined, 1, "the two copies are wired to each other");
assert_eq!(patch.topology.successor(head), Some(tail));
assert_eq!(patch.topology.successor(tail), Some(outside));
}
#[test]
fn pasting_does_not_reach_outside_the_selection() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let head = patch.add_fixture(Fixture::new("head"), LayoutPosition::default());
let outside = patch.add_fixture(Fixture::new("outside"), LayoutPosition::default());
patch.topology.make_successor(head, outside);
state.select_single(head);
transact(&mut patch, &mut state, EditorTransaction::CopySelected);
transact(&mut patch, &mut state, EditorTransaction::Paste);
let pasted = *state
.selected_fixtures
.iter()
.next()
.expect("the pasted fixture is selected");
assert_eq!(patch.topology.successor(pasted), None);
assert_eq!(patch.topology.successor(head), Some(outside));
}
#[test]
fn cutting_removes_the_fixtures_and_keeps_them() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let id = patch.add_fixture(Fixture::new("bar"), LayoutPosition::default());
state.select_single(id);
transact(&mut patch, &mut state, EditorTransaction::CutSelected);
assert!(patch.topology.fixtures.is_empty());
assert_eq!(state.clipboard.gled_fixtures.len(), 1);
transact(&mut patch, &mut state, EditorTransaction::Paste);
assert_eq!(patch.topology.fixtures.len(), 1);
}
#[test]
fn select_all_takes_the_drawn_layouts_members() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let inside = patch.add_fixture(Fixture::new("a"), LayoutPosition::default());
patch.add_fixture(Fixture::new("b"), LayoutPosition::default());
let layout = LayoutRef::Layout(patch.add_layout());
patch.add_member(layout, inside);
transact(&mut patch, &mut state, EditorTransaction::SelectAll);
assert_eq!(state.selected_fixtures.len(), 2, "the topology holds both");
state.select_layout(layout, 0);
transact(&mut patch, &mut state, EditorTransaction::SelectAll);
assert_eq!(state.selected_fixtures, [inside].into_iter().collect());
}
#[test]
fn adding_a_fixture_selects_it() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let add = EditorTransaction::AddFixture(LayoutPosition::default(), None);
transact(&mut patch, &mut state, add);
assert_eq!(patch.topology.fixtures.len(), 1);
assert_eq!(state.selected_fixtures.len(), 1);
}
#[test]
fn removing_in_the_topology_deletes_the_fixture() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let id = patch.add_fixture(Fixture::new("a"), LayoutPosition::default());
state.select_single(id);
transact(&mut patch, &mut state, EditorTransaction::RemoveSelected);
assert!(patch.topology.fixtures.is_empty());
}
#[test]
fn removing_in_a_layout_only_drops_membership() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let id = patch.add_fixture(Fixture::new("a"), LayoutPosition::default());
let layout = LayoutRef::Layout(patch.add_layout());
patch.add_member(layout, id);
state.select_layout(layout, 0);
state.select_single(id);
transact(&mut patch, &mut state, EditorTransaction::RemoveSelected);
assert!(
!patch
.layout(layout)
.expect("the layout was just added")
.contains(id)
);
assert!(
patch.topology.fixtures.contains_key(&id),
"the fixture itself survives"
);
}
#[test]
fn duplicating_is_topology_only() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let id = patch.add_fixture(Fixture::new("a"), LayoutPosition::default());
let layout = LayoutRef::Layout(patch.add_layout());
patch.add_member(layout, id);
state.select_layout(layout, 0);
state.select_single(id);
transact(&mut patch, &mut state, EditorTransaction::DuplicateSelected);
assert_eq!(patch.topology.fixtures.len(), 1);
}
#[test]
fn duplicating_in_the_topology_offsets_the_copy() {
let mut patch = Patch::default();
let mut state = EditorState::default();
let id = patch.add_fixture(Fixture::new("a"), LayoutPosition::default());
state.select_single(id);
transact(&mut patch, &mut state, EditorTransaction::DuplicateSelected);
assert_eq!(patch.topology.fixtures.len(), 2);
let copy = *state
.selected_fixtures
.iter()
.next()
.expect("the copy is selected");
assert_ne!(copy, id);
let original = patch.topology.default_layout.members[&id];
let copied = patch.topology.default_layout.members[©];
assert_eq!(copied.pos, original.pos + PASTE_OFFSET);
}