use concinnity_cook::authoring::world::replace_args;
use concinnity_core::components::Behavior;
use concinnity_core::components::BehaviorLiteral;
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 concinnity_core::components::Sprite;
use concinnity_core::components::Variables;
use concinnity_core::ecs::ComponentAsset;
use concinnity_core::ecs::TraceRequest;
use concinnity_core::ecs::World;
use concinnity_host::thread::asset_id;
use super::fixtures::{behavior, entry, entry_with_args, hook, playing_hook};
use crate::editor::hud::HudAction;
use crate::editor::panels::registry::PanelKey;
use crate::editor::sim;
#[test]
fn transport_keys_play_pause_stop_and_step() {
let mut h = hook(Vec::new());
let key = |k, shift| FrameInput {
ctrl: true,
shift,
key_events: vec![KeyEvent::Press(KeyPress::new(
k,
KeyMods {
ctrl: true,
shift,
..KeyMods::NONE
},
))],
..Default::default()
};
h.sim_keys(&key(InputKey::P, false));
assert!(h.sim.playing(), "Ctrl+P plays");
h.sim_keys(&key(InputKey::P, false));
assert_eq!(h.sim.state, sim::SimState::Paused, "Ctrl+P again pauses");
h.sim_keys(&key(InputKey::Period, false));
assert!(h.sim.take_run_frame(), "Ctrl+Period queues one step");
h.sim_keys(&key(InputKey::P, true));
assert_eq!(h.sim.state, sim::SimState::Stopped, "Ctrl+Shift+P stops");
assert!(
h.rebuild_preview,
"Stop restores through the preview rebuild"
);
crate::editor::hook::tests::fixtures::focus_story(&mut h);
h.sim_keys(&key(InputKey::P, false));
assert_eq!(h.sim.state, sim::SimState::Stopped);
}
#[test]
fn a_held_ctrl_p_toggles_once() {
let mut h = hook(Vec::new());
let press = |repeat| FrameInput {
ctrl: true,
key_events: vec![KeyEvent::Press(KeyPress {
repeat,
..KeyPress::new(InputKey::P, KeyMods::CTRL)
})],
..Default::default()
};
h.sim_keys(&press(false));
assert!(h.sim.playing());
h.sim_keys(&press(true));
h.sim_keys(&press(true));
assert!(h.sim.playing(), "the repeats leave it playing");
}
#[test]
fn transport_chips_drive_the_transport() {
let mut h = hook(Vec::new());
let mut world = World::new();
h.apply_top(HudAction::PlayPause, &mut world);
assert!(h.sim.playing());
h.apply_top(HudAction::Step, &mut world);
assert_eq!(
h.sim.state,
sim::SimState::Paused,
"Step while playing pauses"
);
h.apply_top(HudAction::Stop, &mut world);
assert_eq!(h.sim.state, sim::SimState::Stopped);
assert!(h.rebuild_preview);
}
#[test]
fn an_edit_that_rebuilds_stops_the_simulation() {
let mut h = playing_hook(vec![entry("box", "Prop")]);
let mut world = World::new();
h.entries.push(entry("box2", "Prop"));
h.mark_changed();
assert!(
h.refresh_preview(&mut world),
"a new line needs the world rebuilt"
);
assert_eq!(
h.sim.state,
sim::SimState::Stopped,
"the rebuild discards the run, so the transport says so"
);
}
#[test]
fn an_edit_applied_live_leaves_the_simulation_running() {
let mut h = playing_hook(vec![entry_with_args(
"badge",
"Sprite",
serde_json::json!({ "width": 4.0 }),
)]);
h.world_shadows = Some(Default::default());
let mut world = World::new();
let e = world.push(Sprite::default());
world.identify(e, asset_id::intern("badge"));
h.entries[0]["args"]["width"] = serde_json::json!(16.0);
h.mark_changed();
assert!(
!h.refresh_preview(&mut world),
"a Sprite field is written into the running world"
);
assert!(!h.rebuild_preview, "and the preview is current again");
assert_eq!(h.sim.state, sim::SimState::Playing);
assert_eq!(world.query::<Sprite>().next().unwrap().width, 16.0);
}
fn named_world(assets: Vec<(&str, ComponentAsset)>) -> World {
let mut world = World::new();
for (name, asset) in assets {
world.add(asset, Some(asset_id::intern(name)));
}
world
}
fn behavior_args(nodes: serde_json::Value) -> serde_json::Value {
serde_json::json!({ "on": "tick", "do": nodes })
}
fn set_node(var: &str) -> serde_json::Value {
serde_json::json!({ "set": { "var": var, "value": { "int": 1 } } })
}
#[test]
fn a_behavior_body_edit_is_written_into_the_running_world() {
let mut h = playing_hook(vec![entry_with_args(
"counter",
"Behavior",
behavior_args(serde_json::json!([set_node("n")])),
)]);
h.world_shadows = Some(Default::default());
let mut world = named_world(vec![(
"counter",
ComponentAsset::Behavior(Behavior::default()),
)]);
replace_args(
&mut h.entries[0],
behavior_args(serde_json::json!([set_node("n"), set_node("m")])),
);
h.mark_changed();
assert!(
!h.refresh_preview(&mut world),
"an edited body is written into the running world"
);
assert!(!h.rebuild_preview);
assert_eq!(h.sim.state, sim::SimState::Playing);
assert_eq!(
world.query::<Behavior>().next().unwrap().body.len(),
2,
"the column holds the edited body"
);
}
#[test]
fn a_behavior_edit_that_moves_a_reference_rebuilds() {
let spawning = |template: &str| {
behavior_args(serde_json::json!([
{ "spawn": { "template": template } },
]))
};
let mut h = playing_hook(vec![entry_with_args(
"spawner",
"Behavior",
spawning("crate"),
)]);
h.world_shadows = Some(Default::default());
let mut world = named_world(vec![(
"spawner",
ComponentAsset::Behavior(Behavior::default()),
)]);
replace_args(&mut h.entries[0], spawning("barrel"));
h.mark_changed();
assert!(
h.refresh_preview(&mut world),
"a retargeted spawn needs the world rebuilt"
);
}
#[test]
fn a_variables_edit_is_written_into_the_running_world() {
let table = |value: i64| serde_json::json!({ "vars": [{ "name": "score", "value": { "int": value } }] });
let mut h = playing_hook(vec![entry_with_args("world_vars", "Variables", table(0))]);
h.world_shadows = Some(Default::default());
let mut world = named_world(vec![(
"world_vars",
ComponentAsset::Variables(Variables::default()),
)]);
replace_args(&mut h.entries[0], table(7));
h.mark_changed();
assert!(
!h.refresh_preview(&mut world),
"a declaration is written into the running world"
);
assert!(!h.rebuild_preview);
assert_eq!(h.sim.state, sim::SimState::Playing);
let declared = world.query::<Variables>().next().unwrap();
assert_eq!(declared.vars.len(), 1);
assert_eq!(declared.vars[0].value, BehaviorLiteral::Int(7));
}
#[test]
fn stop_rebuilds_even_though_nothing_was_authored() {
let mut h = playing_hook(vec![entry_with_args(
"badge",
"Sprite",
serde_json::json!({ "width": 4.0 }),
)]);
h.world_shadows = Some(Default::default());
let mut world = World::new();
h.sim_stop();
assert!(h.refresh_preview(&mut world), "Stop always rebuilds");
}
#[test]
fn entering_play_ends_the_fly_camera_and_vice_versa() {
let mut h = hook(Vec::new());
h.toggle_fly();
assert!(h.fly);
h.sim_toggle_play();
assert!(h.sim.playing() && !h.fly, "play takes the cursor from fly");
h.toggle_fly();
assert!(h.fly);
assert_eq!(
h.sim.state,
sim::SimState::Paused,
"fly pauses a running world"
);
}
#[test]
fn the_trace_request_follows_the_live_debug_panels() {
let mut h = hook(vec![behavior(
"b",
serde_json::json!({
"on": "start", "do": [{"save": {}}],
}),
)]);
let mut world = World::new();
h.drive_trace(&mut world);
assert!(
world.resource::<TraceRequest>().is_none(),
"no panel open, no request"
);
h.open[PanelKey::Behavior] = true;
h.drive_trace(&mut world);
assert!(world.resource::<TraceRequest>().is_some());
h.open[PanelKey::Behavior] = false;
h.drive_trace(&mut world);
assert!(
world.resource::<TraceRequest>().is_none(),
"closing the panels withdraws it"
);
}