use concinnity_core::components::TextLabel;
use concinnity_core::ecs::World;
use serde_json::json;
use crate::editor::asset_handle::AssetHandle;
use crate::editor::behavior::chart::{self, ChartIds};
use crate::editor::behavior::graph::{Card, Chart};
use crate::editor::hook::EditorHook;
use crate::editor::hook::tests::fixtures::{
active, behavior, drag_input, entry, entry_target, entry_with_args, hook, select, selected,
};
use crate::editor::map;
use crate::editor::panels::registry::{self, PanelKey};
const VP: [f32; 2] = [1280.0, 720.0];
fn map_session(entries: Vec<serde_json::Value>) -> (EditorHook, World) {
let mut world = injected_world();
let mut h = hook(entries);
registry::panel(PanelKey::Map).toggle(&mut h, &mut world);
(h, world)
}
fn injected_world() -> World {
registry::panel(PanelKey::Map).ids().test_world()
}
fn card<'a>(chart: &'a Chart, title: &str) -> &'a Card {
chart
.cards
.iter()
.find(|c| c.title == title)
.unwrap_or_else(|| panic!("no `{title}` card in {:?}", titles(chart)))
}
fn titles(chart: &Chart) -> Vec<&str> {
chart.cards.iter().map(|c| c.title.as_str()).collect()
}
fn canvas(h: &EditorHook) -> [f32; 2] {
map::panel::canvas(h.effective_size(PanelKey::Map))
}
fn card_center(h: &EditorHook, title: &str) -> [f32; 2] {
let chart = h.map_chart();
let o = h.origin(PanelKey::Map, VP);
let band = map::panel::band(o, h.effective_size(PanelKey::Map));
let r = chart::card_rect(card(&chart, title), band, h.map.pan);
[r[0] + r[2] * 0.5, r[1] + r[3] * 0.5]
}
fn click_card(h: &mut EditorHook, world: &mut World, title: &str) -> bool {
let at = card_center(h, title);
let o = h.origin(PanelKey::Map, VP);
registry::panel(PanelKey::Map).press(h, world, at[0], at[1], o)
}
fn menu_world() -> Vec<serde_json::Value> {
vec![
entry("bistro", "Scene"),
entry_with_args(
"main",
"MainMenu",
json!({"initial": true, "items": [{"label": "Start", "action": {"scene": "bistro"}}]}),
),
]
}
#[test]
fn the_working_entries_become_the_worlds_map() {
let (h, _world) = map_session(menu_world());
let chart = h.map_chart();
assert_eq!(titles(&chart), ["main", "bistro"]);
assert_eq!(card(&chart, "main").column, 0, "the world starts there");
assert_eq!(card(&chart, "bistro").column, 1);
let start = &chart.wires[0];
assert_eq!(start.label.as_deref(), Some("Start"));
}
#[test]
fn a_card_carries_the_key_of_the_entry_declaring_its_place() {
let (h, _world) = map_session(menu_world());
let key = h.entries.key_at(0).unwrap();
assert_eq!(
card(&h.map_chart(), "bistro").handle,
Some(AssetHandle::Entry(key))
);
}
#[test]
fn an_anonymous_entry_maps_under_the_label_the_build_gives_it() {
let (h, _world) = map_session(vec![json!({"type": "Scene", "args": {}})]);
let chart = h.map_chart();
assert_eq!(titles(&chart), ["Scene#0"]);
assert_eq!(
card(&chart, "Scene#0").handle,
Some(AssetHandle::Entry(h.entries.key_at(0).unwrap()))
);
}
#[test]
fn an_entry_of_no_registered_type_is_left_out() {
let (h, _world) = map_session(vec![
entry("bistro", "Scene"),
json!({"type": "Nonsense", "args": {"$id": "x", "scene": "bistro"}}),
]);
let chart = h.map_chart();
assert_eq!(titles(&chart), ["bistro"]);
assert_eq!(card(&chart, "bistro").detail, "scene");
}
fn scenes(count: usize) -> Vec<serde_json::Value> {
(0..count)
.map(|i| entry(&format!("scene_{i}"), "Scene"))
.collect()
}
#[test]
fn the_wheel_pans_the_canvas_and_stops_at_the_maps_edge() {
let (mut h, mut world) = map_session(scenes(20));
let p = registry::panel(PanelKey::Map);
p.scroll(&mut h, &mut world, 1.0);
assert!(h.map.pan[1] > 0.0, "a column of places pans down");
assert_eq!(h.map.pan[0], 0.0, "there is only the one column");
for _ in 0..64 {
p.scroll(&mut h, &mut world, 1.0);
}
let bottom = h.map.pan;
p.scroll(&mut h, &mut world, 1.0);
assert_eq!(h.map.pan, bottom, "the map's foot is the end of the pan");
for _ in 0..128 {
p.scroll(&mut h, &mut world, -1.0);
}
assert_eq!(h.map.pan, [0.0, 0.0]);
}
#[test]
fn a_press_on_the_canvas_drags_the_map_under_the_cursor() {
let (mut h, mut world) = map_session(scenes(20));
let p = registry::panel(PanelKey::Map);
let vp = [1280.0, 720.0];
let o = h.origin(PanelKey::Map, vp);
let grab = [o[0] + 400.0, o[1] + 120.0];
assert!(p.press(&mut h, &mut world, grab[0], grab[1], o));
h.drive_map_pan(&drag_input([grab[0], grab[1] - 40.0], true));
assert_eq!(h.map.pan[1], 40.0, "the map followed the cursor");
h.drive_map_pan(&drag_input([grab[0], grab[1] - 80.0], false));
assert_eq!(h.map.pan[1], 40.0, "the release left it where it was");
assert!(h.map.pan_drag.is_none());
}
#[test]
fn the_map_and_the_behavior_chart_draw_at_once() {
let mut world_entries = menu_world();
world_entries.push(behavior(
"greeter",
json!({"on": "start", "do": [{"show": {"target": "self"}}]}),
));
let mut h = hook(world_entries);
let behavior = registry::panel(PanelKey::Behavior);
let map = registry::panel(PanelKey::Map);
let mut world = behavior.ids().test_world();
for &id in &map.ids().sprites {
world.push_identified(id, concinnity_core::components::Sprite::default());
}
for &id in &map.ids().labels {
world.push_identified(id, TextLabel::default());
}
behavior.toggle(&mut h, &mut world);
map.toggle(&mut h, &mut world);
h.behavior.mode = crate::editor::behavior::panel::ViewMode::Chart;
let vp = [1600.0, 900.0];
map.draw(&h, &mut world, h.origin(PanelKey::Map, vp), [-1.0, -1.0]);
behavior.draw(
&h,
&mut world,
h.origin(PanelKey::Behavior, vp),
[-1.0, -1.0],
);
let drawn = |ids: ChartIds| -> Vec<String> {
(0..4)
.map(|i| world.get_by_id::<TextLabel>(ids.card_title(i)).unwrap())
.filter(|l| l.visible && !l.content.is_empty())
.map(|l| l.content.clone())
.collect()
};
assert!(
drawn(map::panel::CHART_IDS).contains(&"main".to_string()),
"the map lost its cards to the behavior chart",
);
assert!(
!drawn(crate::editor::behavior::panel::CHART_IDS).is_empty(),
"the behavior chart drew nothing, so the map proves nothing",
);
}
#[test]
fn a_click_on_a_place_selects_it_and_opens_it_for_editing() {
let (mut h, mut world) = map_session(menu_world());
assert!(click_card(&mut h, &mut world, "bistro"));
assert_eq!(selected(&h), ["bistro"]);
assert_eq!(h.form.target, entry_target(&h, 0));
assert_eq!(h.form.selected_type.as_deref(), Some("Scene"));
}
#[test]
fn a_shift_click_adds_a_place_to_the_selection_and_takes_it_away_again() {
let (mut h, mut world) = map_session(menu_world());
click_card(&mut h, &mut world, "bistro");
h.shift_held = true;
click_card(&mut h, &mut world, "main");
assert_eq!(selected(&h), ["bistro", "main"]);
assert_eq!(active(&h).as_deref(), Some("main"));
click_card(&mut h, &mut world, "main");
assert_eq!(selected(&h), ["bistro"]);
}
#[test]
fn a_click_on_a_place_the_world_does_not_declare_selects_nothing() {
let (mut h, mut world) = map_session(vec![entry_with_args(
"main",
"MainMenu",
json!({"initial": true, "items": [{"label": "Start", "action": {"scene": "nowhere"}}]}),
)]);
assert!(click_card(&mut h, &mut world, "nowhere"));
assert!(h.selection.is_empty(), "{:?}", selected(&h));
}
#[test]
fn a_click_on_a_place_the_build_generates_selects_it_under_that_identity() {
let (mut h, mut world) = map_session(vec![entry_with_args(
"main",
"MainMenu",
json!({"initial": true, "items": [{"label": "Settings", "action": "settings"}]}),
)]);
let generated = h
.map_chart()
.cards
.iter()
.find(|c| matches!(c.handle, Some(AssetHandle::Generated(_))))
.expect("the settings screen the menu opens")
.title
.clone();
assert!(click_card(&mut h, &mut world, &generated));
assert_eq!(
h.selection.active(),
Some(&AssetHandle::Generated(generated))
);
}
#[test]
fn a_ctrl_click_on_a_place_reaches_the_behaviors_that_send_the_world_to_it() {
let (mut h, mut world) = map_session(vec![
entry("bistro", "Scene"),
behavior(
"opener",
json!({"on": "start", "do": [{"scene": {"scene": "bistro"}}]}),
),
behavior("idler", json!({"on": "tick", "do": []})),
]);
h.ctrl_held = true;
assert!(click_card(&mut h, &mut world, "bistro"));
assert_eq!(
selected(&h),
["opener"],
"the one that sends the world here"
);
assert!(h.open[PanelKey::Behavior], "the panel showing one opened");
assert_eq!(h.behavior_data().name, "opener");
}
#[test]
fn a_ctrl_click_on_a_place_no_behavior_reaches_leaves_the_selection_alone() {
let (mut h, mut world) = map_session(menu_world());
click_card(&mut h, &mut world, "main");
h.ctrl_held = true;
assert!(click_card(&mut h, &mut world, "bistro"));
assert_eq!(selected(&h), ["main"]);
assert!(!h.open[PanelKey::Behavior], "nothing to open");
}
#[test]
fn a_selection_made_elsewhere_lights_up_its_card() {
let (mut h, _world) = map_session(menu_world());
select(&mut h, &["bistro"]);
let chart = h.map_chart();
let at = chart.cards.iter().position(|c| c.title == "bistro");
assert_eq!(h.make_map_view(&chart, [-1.0, -1.0]).selected, at);
}
#[test]
fn a_selection_naming_no_place_lights_up_nothing() {
let mut entries = menu_world();
entries.push(entry_with_args(
"floor",
"Prop",
json!({"mesh": "floor_mesh"}),
));
let (mut h, _world) = map_session(entries);
select(&mut h, &["floor"]);
let chart = h.map_chart();
assert_eq!(h.make_map_view(&chart, [-1.0, -1.0]).selected, None);
}
#[test]
fn a_selection_off_the_canvas_pans_the_map_to_it() {
let (mut h, _world) = map_session(scenes(20));
h.drive_map();
assert_eq!(h.map.pan, [0.0, 0.0]);
select(&mut h, &["scene_19"]);
h.drive_map();
assert!(
h.map.pan[1] > 0.0,
"the canvas did not follow the selection"
);
let chart = h.map_chart();
assert_eq!(
chart::pan_to(card(&chart, "scene_19"), canvas(&h), h.map.pan, &chart),
h.map.pan,
"it stopped short of the place it was following",
);
}
#[test]
fn a_selection_already_in_view_leaves_the_canvas_where_it_is() {
let mut entries = scenes(20);
entries.push(entry_with_args(
"floor",
"Prop",
json!({"mesh": "floor_mesh"}),
));
let (mut h, _world) = map_session(entries);
select(&mut h, &["scene_19"]);
h.drive_map();
let at = h.map.pan;
select(&mut h, &["scene_18"]);
h.drive_map();
assert_eq!(
h.map.pan, at,
"a neighbor already in view re-centered the map"
);
select(&mut h, &["floor"]);
h.drive_map();
assert_eq!(h.map.pan, at, "a prop is inside a place, not one of them");
}
#[test]
fn reopening_the_panel_puts_the_canvas_back_on_where_the_world_starts() {
let (mut h, mut world) = map_session(scenes(20));
let p = registry::panel(PanelKey::Map);
for _ in 0..8 {
p.scroll(&mut h, &mut world, 1.0);
}
assert!(h.map.pan[1] > 0.0, "nothing moved, so nothing is proven");
p.close(&mut h, &mut world);
p.toggle(&mut h, &mut world);
h.drive_map();
assert_eq!(h.map.pan, [0.0, 0.0]);
}
#[test]
fn a_map_that_shrank_under_the_canvas_brings_it_back_to_the_last_place() {
let (mut h, mut world) = map_session(scenes(20));
let p = registry::panel(PanelKey::Map);
for _ in 0..16 {
p.scroll(&mut h, &mut world, 1.0);
}
let deep = h.map.pan;
for _ in 3..20 {
h.entries.remove(3);
}
h.drive_map();
assert!(
h.map.pan[1] < deep[1],
"the canvas stayed past the map's foot"
);
let chart = h.map_chart();
assert_eq!(chart::clamp_pan(h.map.pan, &chart, canvas(&h)), h.map.pan);
}
#[test]
fn a_closed_panel_drives_nothing() {
let mut h = hook(scenes(20));
assert!(
!h.open[PanelKey::Map],
"the Map panel is closed until it is opened"
);
select(&mut h, &["scene_19"]);
h.drive_map();
assert_eq!(h.map.pan, [0.0, 0.0]);
assert!(!h.map.rooted);
assert_eq!(selected(&h), ["scene_19"], "the selection is not the map's");
}