use concinnity_core::components::FrameInput;
use concinnity_core::components::InputKey;
use concinnity_core::components::KeyEvent;
use concinnity_core::components::KeyMods;
use concinnity_core::components::KeyPress;
use super::fixtures::{
behavior, behavior_escape_input, behavior_session, open_args, press_behavior_key, press_remove,
select_behavior, type_name,
};
use crate::editor::behavior;
use crate::editor::behavior::panel::{BehaviorAction, ViewMode};
use crate::editor::hook::EditorHook;
use crate::editor::panels::registry::PanelKey;
use crate::editor::widget;
fn selected_card_title(h: &EditorHook) -> Option<String> {
let data = h.behavior_data();
data.card
.and_then(|i| data.chart.cards.get(i))
.map(|c| c.title.clone())
}
fn selected_overview_title(h: &EditorHook) -> Option<String> {
let data = h.behavior_data();
h.behavior
.overview_card
.and_then(|i| data.overview.cards.get(i))
.map(|c| c.title.clone())
}
#[test]
fn behavior_arrows_step_the_outline_one_row_at_a_time() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": [{"save": {}}, {"hide": {"target": "self"}}]}),
)]);
assert_eq!(h.behavior.row, None);
press_behavior_key(&mut h, &mut world, InputKey::Down);
assert_eq!(h.behavior.row, Some(0));
press_behavior_key(&mut h, &mut world, InputKey::Down);
assert_eq!(h.behavior.row, Some(1));
press_behavior_key(&mut h, &mut world, InputKey::Up);
assert_eq!(h.behavior.row, Some(0));
press_behavior_key(&mut h, &mut world, InputKey::Up);
assert_eq!(h.behavior.row, Some(0), "the top of the list does not wrap");
press_behavior_key(&mut h, &mut world, InputKey::Right);
assert_eq!(h.behavior.row, Some(0));
}
#[test]
fn behavior_arrows_scroll_the_outline_to_keep_the_selection_showing() {
let body: Vec<serde_json::Value> = (0..30).map(|_| serde_json::json!({"save": {}})).collect();
let (mut h, mut world) = behavior_session(vec![behavior(
"long",
serde_json::json!({"on": "start", "do": body}),
)]);
for _ in 0..25 {
press_behavior_key(&mut h, &mut world, InputKey::Down);
}
let row = h.behavior.row.expect("a row is selected");
assert_eq!(row, 24);
assert!(h.behavior.scroll > 0, "the window followed the selection");
assert!(row >= h.behavior.scroll, "row {row} is above the window");
}
#[test]
fn behavior_arrows_follow_the_chart_chain_and_cross_its_branches() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": [
{"if": {
"cond": {"bool": true},
"then": [{"show": {"target": "self"}}],
"else": [{"hide": {"target": "self"}}],
}},
{"save": {}},
]}),
)]);
h.apply_behavior_action(BehaviorAction::ToggleView, &mut world, [0.0, 0.0]);
assert_eq!(h.behavior.mode, ViewMode::Chart);
press_behavior_key(&mut h, &mut world, InputKey::Right);
assert_eq!(selected_card_title(&h).as_deref(), Some("on tick"));
press_behavior_key(&mut h, &mut world, InputKey::Right);
assert_eq!(selected_card_title(&h).as_deref(), Some("if"));
press_behavior_key(&mut h, &mut world, InputKey::Right);
assert_eq!(selected_card_title(&h).as_deref(), Some("show"));
press_behavior_key(&mut h, &mut world, InputKey::Down);
assert_eq!(selected_card_title(&h).as_deref(), Some("hide"));
press_behavior_key(&mut h, &mut world, InputKey::Up);
assert_eq!(selected_card_title(&h).as_deref(), Some("show"));
press_behavior_key(&mut h, &mut world, InputKey::Left);
assert_eq!(selected_card_title(&h).as_deref(), Some("if"));
}
#[test]
fn behavior_arrows_step_the_overview_and_enter_opens_a_behavior() {
let (mut h, mut world) = behavior_session(vec![
behavior(
"award",
serde_json::json!({"on": "start",
"do": [{"set": {"var": "score", "value": {"int": 1}}}]}),
),
behavior(
"react",
serde_json::json!({"on": {"variable": "score"}, "do": []}),
),
]);
for _ in 0..2 {
h.apply_behavior_action(BehaviorAction::ToggleView, &mut world, [0.0, 0.0]);
}
assert_eq!(h.behavior.mode, ViewMode::Overview);
assert_eq!(
selected_overview_title(&h).as_deref(),
Some("award"),
"the map opens on the behavior that was showing"
);
press_behavior_key(&mut h, &mut world, InputKey::Right);
assert_eq!(selected_overview_title(&h).as_deref(), Some("score"));
press_behavior_key(&mut h, &mut world, InputKey::Enter);
assert_eq!(h.behavior.mode, ViewMode::Overview);
assert!(
h.open[PanelKey::Variables],
"the table the variable is declared in"
);
press_behavior_key(&mut h, &mut world, InputKey::Right);
assert_eq!(selected_overview_title(&h).as_deref(), Some("react"));
press_behavior_key(&mut h, &mut world, InputKey::Enter);
assert_eq!(h.behavior.index, 1);
assert_eq!(h.behavior_data().name, "react");
assert_eq!(h.behavior.mode, ViewMode::Chart);
}
#[test]
fn the_overview_keyboard_steps_on_from_the_card_last_reached() {
let (mut h, mut world) = behavior_session(vec![
serde_json::json!({"type": "Scene", "args": {"$id": "hub"}}),
behavior(
"finish",
serde_json::json!({"on": "start", "do": [{"scene": {"scene": "hub"}}]}),
),
]);
for _ in 0..2 {
h.apply_behavior_action(BehaviorAction::ToggleView, &mut world, [0.0, 0.0]);
}
let hub = h
.behavior_data()
.overview
.cards
.iter()
.position(|c| c.title == "hub")
.expect("the scene the behavior sends the world to");
h.apply_behavior_action(BehaviorAction::SelectAsset(hub), &mut world, [0.0, 0.0]);
assert_eq!(selected_overview_title(&h).as_deref(), Some("hub"));
press_behavior_key(&mut h, &mut world, InputKey::Left);
assert_eq!(selected_overview_title(&h).as_deref(), Some("finish"));
}
#[test]
fn behavior_enter_opens_the_palette_and_its_arrows_pick_from_it() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": []}),
)]);
select_behavior(&mut h, &mut world, "do");
assert!(!h.behavior.focus, "a list row takes no typed value");
press_behavior_key(&mut h, &mut world, InputKey::Enter);
assert!(h.behavior.picking, "Enter opened the palette");
assert_eq!(h.behavior.pick, 0);
let second = h.behavior_data().picks[1].verb;
press_behavior_key(&mut h, &mut world, InputKey::Down);
assert_eq!(h.behavior.pick, 1);
press_behavior_key(&mut h, &mut world, InputKey::Enter);
assert!(!h.behavior.picking, "picking closed the palette");
let body = open_args(&h)["do"].clone();
assert!(
body[0].get(second).is_some(),
"the highlighted option is the one that landed: {body:?}"
);
}
#[test]
fn behavior_enter_on_a_row_with_no_options_opens_nothing() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": [{"let": {"name": "t", "value": {"int": 1}}}]}),
)]);
select_behavior(&mut h, &mut world, "name");
assert!(h.behavior_data().picks.is_empty());
press_behavior_key(&mut h, &mut world, InputKey::Enter);
assert!(!h.behavior.picking);
}
#[test]
fn behavior_palette_highlight_scrolls_itself_into_the_window() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": []}),
)]);
select_behavior(&mut h, &mut world, "do");
press_behavior_key(&mut h, &mut world, InputKey::Enter);
let total = h.behavior_data().picks.len();
assert!(
total > behavior::panel::PICK_POOL,
"the node vocabulary overflows the palette"
);
for _ in 0..behavior::panel::PICK_POOL {
press_behavior_key(&mut h, &mut world, InputKey::Down);
}
assert_eq!(h.behavior.pick, behavior::panel::PICK_POOL);
assert!(h.behavior.pick_scroll > 0, "the window followed it down");
assert!(h.behavior.pick >= h.behavior.pick_scroll);
assert!(h.behavior.pick < h.behavior.pick_scroll + behavior::panel::PICK_POOL);
for _ in 0..behavior::panel::PICK_POOL {
press_behavior_key(&mut h, &mut world, InputKey::Up);
}
assert_eq!(h.behavior.pick, 0);
assert_eq!(h.behavior.pick_scroll, 0);
}
#[test]
fn behavior_escape_clears_one_waiting_state_at_a_time() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": [{"let": {"name": "t", "value": {"int": 1}}}]}),
)]);
select_behavior(&mut h, &mut world, "do");
press_behavior_key(&mut h, &mut world, InputKey::Enter);
assert!(h.behavior.picking);
h.behavior_keys(&mut world, &behavior_escape_input());
assert!(!h.behavior.picking, "the palette closed without picking");
assert_eq!(open_args(&h)["do"].as_array().map(Vec::len), Some(1));
press_remove(&mut h, &mut world);
assert!(h.behavior.remove_armed);
h.behavior_keys(&mut world, &behavior_escape_input());
assert!(!h.behavior.remove_armed, "the armed removal was canceled");
assert_eq!(h.behavior_entries().len(), 1);
select_behavior(&mut h, &mut world, "name");
assert!(h.behavior.focus, "a text row takes the value field");
h.behavior_keys(&mut world, &behavior_escape_input());
assert!(!h.behavior.focus, "the value field gave the keyboard up");
}
#[test]
fn behavior_escape_reverts_an_abandoned_rename() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": []}),
)]);
h.apply_behavior_action(BehaviorAction::FocusName, &mut world, [0.0, 0.0]);
type_name(&mut world, "half typed");
h.behavior_keys(&mut world, &behavior_escape_input());
assert!(!h.behavior.name_focus);
assert_eq!(h.behavior_data().name, "chase");
assert_eq!(
widget::field_text(&world, behavior::panel::NAME_INPUT),
"chase",
);
}
#[test]
fn behavior_horizontal_keys_stay_with_the_caret_while_a_value_is_focused() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": [{"let": {"name": "t", "value": {"int": 1}}}]}),
)]);
select_behavior(&mut h, &mut world, "name");
assert!(h.behavior.focus);
let row = h.behavior.row;
for key in [InputKey::Left, InputKey::Right] {
press_behavior_key(&mut h, &mut world, key);
assert_eq!(h.behavior.row, row, "{key:?} moved the selection");
}
press_behavior_key(&mut h, &mut world, InputKey::Down);
assert_ne!(h.behavior.row, row, "Down still steps the outline");
}
#[test]
fn behavior_name_field_holds_the_arrows_until_it_is_given_up() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": [{"save": {}}]}),
)]);
h.apply_behavior_action(BehaviorAction::FocusName, &mut world, [0.0, 0.0]);
press_behavior_key(&mut h, &mut world, InputKey::Down);
assert_eq!(h.behavior.row, None, "the arrows did not reach the outline");
h.behavior_keys(&mut world, &behavior_escape_input());
press_behavior_key(&mut h, &mut world, InputKey::Down);
assert_eq!(h.behavior.row, Some(0));
}
#[test]
fn behavior_tab_cycles_through_the_three_views() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": [{"hide": {"target": "self"}}]}),
)]);
select_behavior(&mut h, &mut world, "hide");
let selected = h.behavior.row;
assert_eq!(h.behavior.mode, ViewMode::Outline);
for want in [ViewMode::Chart, ViewMode::Overview, ViewMode::Outline] {
press_behavior_key(&mut h, &mut world, InputKey::Tab);
assert_eq!(h.behavior.mode, want);
assert_eq!(
h.behavior.row, selected,
"the selection survives the switch"
);
}
}
#[test]
fn behavior_tab_leaves_the_view_alone_while_the_name_field_is_focused() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": []}),
)]);
h.apply_behavior_action(BehaviorAction::FocusName, &mut world, [0.0, 0.0]);
type_name(&mut world, "half typed");
press_behavior_key(&mut h, &mut world, InputKey::Tab);
assert_eq!(h.behavior.mode, ViewMode::Outline);
assert!(h.behavior.name_focus, "the field kept the keyboard");
h.behavior_keys(&mut world, &behavior_escape_input());
press_behavior_key(&mut h, &mut world, InputKey::Tab);
assert_eq!(h.behavior.mode, ViewMode::Chart);
}
#[test]
fn behavior_tab_does_nothing_while_the_palette_is_open() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "tick", "do": []}),
)]);
select_behavior(&mut h, &mut world, "do");
press_behavior_key(&mut h, &mut world, InputKey::Enter);
assert!(h.behavior.picking);
press_behavior_key(&mut h, &mut world, InputKey::Tab);
assert_eq!(h.behavior.mode, ViewMode::Outline);
assert!(h.behavior.picking, "the palette is still up");
assert_eq!(open_args(&h)["do"].as_array().map(Vec::len), Some(0));
}
fn ctrl_key_input(key: InputKey) -> FrameInput {
FrameInput {
key_events: vec![KeyEvent::Press(KeyPress::new(key, KeyMods::CTRL))],
ctrl: true,
viewport: [1280.0, 720.0],
..Default::default()
}
}
fn body_verbs(h: &EditorHook) -> Vec<String> {
open_args(h)["do"]
.as_array()
.map(|a| {
a.iter()
.filter_map(|n| n.as_object()?.keys().next().cloned())
.collect()
})
.unwrap_or_default()
}
#[test]
fn behavior_duplicate_copies_a_node_subtree_next_to_it() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "start", "do": [
{"if": {"cond": {"bool": true}, "then": [{"hide": {"target": "self"}}]}},
{"save": {}},
]}),
)]);
select_behavior(&mut h, &mut world, "if");
h.apply_behavior_action(BehaviorAction::Duplicate, &mut world, [0.0, 0.0]);
assert_eq!(body_verbs(&h), ["if", "if", "save"]);
assert_eq!(
open_args(&h)["do"][1]["if"]["then"][0]["hide"]["target"],
serde_json::json!("self"),
"the branch came with it"
);
let row = h.behavior.row.expect("the copy is selected");
assert_eq!(
h.behavior_rows()[row].element,
Some(vec![
crate::editor::behavior::path::field("do"),
crate::editor::behavior::path::Step::Index(1),
]),
);
}
#[test]
fn behavior_ctrl_c_holds_a_node_and_ctrl_v_places_it() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "start", "do": [{"save": {}}, {"hide": {"target": "self"}}]}),
)]);
select_behavior(&mut h, &mut world, "hide");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::C));
assert!(h.behavior.clip.is_some(), "the node is held");
assert_eq!(body_verbs(&h), ["save", "hide"], "copying wrote nothing");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::V));
assert_eq!(body_verbs(&h), ["save", "hide", "hide"]);
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::V));
assert_eq!(body_verbs(&h), ["save", "hide", "hide", "hide"]);
}
#[test]
fn behavior_duplicate_leaves_what_is_held_alone() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "start", "do": [{"save": {}}, {"hide": {"target": "self"}}]}),
)]);
select_behavior(&mut h, &mut world, "save");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::C));
select_behavior(&mut h, &mut world, "hide");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::D));
assert_eq!(body_verbs(&h), ["save", "hide", "hide"]);
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::V));
assert_eq!(body_verbs(&h), ["save", "hide", "hide", "save"]);
}
#[test]
fn behavior_clipboard_carries_a_node_to_another_behavior() {
let (mut h, mut world) = behavior_session(vec![
behavior(
"chase",
serde_json::json!({"on": "start", "do": [{"hide": {"target": "self"}}]}),
),
behavior("greet", serde_json::json!({"on": "tick", "do": []})),
]);
select_behavior(&mut h, &mut world, "hide");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::C));
h.apply_behavior_action(BehaviorAction::Step(1), &mut world, [0.0, 0.0]);
assert_eq!(h.behavior_data().name, "greet");
assert!(h.behavior.clip.is_some(), "opening another kept it");
select_behavior(&mut h, &mut world, "do");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::V));
assert_eq!(body_verbs(&h), ["hide"]);
}
#[test]
fn behavior_paste_is_refused_by_a_list_of_another_kind() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "start", "scope": ["Prop"], "do": [{"save": {}}]}),
)]);
select_behavior(&mut h, &mut world, "save");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::C));
select_behavior(&mut h, &mut world, "scope");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::V));
assert_eq!(open_args(&h)["scope"], serde_json::json!(["Prop"]));
assert_eq!(body_verbs(&h), ["save"], "and the body is untouched too");
}
#[test]
fn behavior_copy_of_a_non_member_holds_nothing() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "start", "do": [{"save": {}}]}),
)]);
select_behavior(&mut h, &mut world, "on");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::C));
assert!(h.behavior.clip.is_none());
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::D));
assert_eq!(
body_verbs(&h),
["save"],
"and there is nothing to duplicate"
);
}
#[test]
fn behavior_clipboard_keys_stand_down_while_a_field_is_focused() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "start", "do": [{"let": {"name": "t", "value": {"int": 1}}}]}),
)]);
select_behavior(&mut h, &mut world, "name");
assert!(h.behavior.focus, "a text row takes the value field");
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::D));
assert_eq!(body_verbs(&h), ["let"], "nothing was duplicated");
h.apply_behavior_action(BehaviorAction::FocusName, &mut world, [0.0, 0.0]);
h.behavior_keys(&mut world, &ctrl_key_input(InputKey::D));
assert_eq!(body_verbs(&h), ["let"]);
}
#[test]
fn behavior_duplicate_is_undoable() {
let (mut h, mut world) = behavior_session(vec![behavior(
"chase",
serde_json::json!({"on": "start", "do": [{"save": {}}]}),
)]);
select_behavior(&mut h, &mut world, "save");
h.apply_behavior_action(BehaviorAction::Duplicate, &mut world, [0.0, 0.0]);
assert_eq!(body_verbs(&h), ["save", "save"]);
h.undo(&mut world);
assert_eq!(body_verbs(&h), ["save"]);
}