use delvewright_dsl::envelope::Stage;
use delvewright_dsl::{DSL_VERSION, stage_schema};
use serde_json::Value;
fn quests() -> Value {
stage_schema(Stage::Quests)
}
fn props(schema: &Value, def: &str) -> Vec<String> {
let mut v: Vec<String> = schema["$defs"][def]["properties"]
.as_object()
.unwrap_or_else(|| panic!("`{def}` declares no properties"))
.keys()
.cloned()
.collect();
v.sort();
v
}
fn tags(schema: &Value, def: &str, tag: &str) -> Vec<String> {
schema["$defs"][def]["oneOf"]
.as_array()
.unwrap_or_else(|| panic!("`{def}` is not a tagged union"))
.iter()
.filter_map(|b| b["properties"][tag]["const"].as_str().map(str::to_string))
.collect()
}
fn branch<'a>(schema: &'a Value, def: &str, tag: &str, value: &str) -> &'a Value {
schema["$defs"][def]["oneOf"]
.as_array()
.unwrap()
.iter()
.find(|b| b["properties"][tag]["const"] == value)
.unwrap_or_else(|| panic!("`{def}` has no `{value}` branch"))
}
#[test]
fn the_quests_stage_declares_assemblies() {
let s = quests();
let content = s["properties"]["content"]["$ref"]
.as_str()
.map(|r| r.trim_start_matches("#/$defs/").to_string())
.unwrap_or_else(|| "QuestsContent".to_string());
assert!(
props(&s, &content).contains(&"assemblies".to_string()),
"{:?}",
props(&s, &content)
);
assert_eq!(
props(&s, "Assembly"),
["at", "facing", "hitbox", "id", "initial", "rig", "strikes"]
);
assert_eq!(props(&s, "AssemblyHitbox"), ["height", "offset", "width"]);
assert_eq!(props(&s, "AssemblyStrikes"), ["aim", "pattern", "while_in"]);
assert_eq!(props(&s, "StrikeAim"), ["facings"]);
assert_eq!(
props(&s, "StrikeStep"),
[
"hold",
"lock",
"on_land",
"strike",
"ticks_per_frame",
"windup"
]
);
let at = &s["$defs"]["Assembly"]["properties"]["at"];
assert!(
at.to_string().contains("#/$defs/Mark"),
"`at` is a Mark: {at}"
);
let while_in = &s["$defs"]["AssemblyStrikes"]["properties"]["while_in"];
assert!(
while_in.to_string().contains("#/$defs/StealthZone"),
"{while_in}"
);
}
#[test]
fn the_effect_union_gains_three_verbs() {
let s = quests();
let verbs = tags(&s, "QuestEffect", "type");
for v in ["spawn-assembly", "despawn-assembly", "play-clip"] {
assert!(verbs.contains(&v.to_string()), "{v} missing: {verbs:?}");
}
let play = branch(&s, "QuestEffect", "type", "play-clip");
let mut fields: Vec<&str> = play["properties"]
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.filter(|k| *k != "type")
.collect();
fields.sort();
assert!(fields.starts_with(&["assembly", "clip"]), "{fields:?}");
}
#[test]
fn a_strike_step_can_lock_and_a_pattern_can_be_rearmed() {
let s = quests();
assert_eq!(props(&s, "StrikeLock"), ["pick", "reaches", "within"]);
let within = &s["$defs"]["StrikeLock"]["properties"]["within"];
assert!(
within.to_string().contains("#/$defs/StealthZone"),
"{within}"
);
let picks: Vec<String> = s["$defs"]["LockPick"]["oneOf"]
.as_array()
.expect("LockPick is a documented unit enum")
.iter()
.map(|b| b["const"].as_str().unwrap().to_string())
.collect();
assert_eq!(picks, ["nearest", "furthest", "random"]);
let verbs = tags(&s, "QuestEffect", "type");
assert!(verbs.contains(&"arm-strikes".to_string()), "{verbs:?}");
let arm = branch(&s, "QuestEffect", "type", "arm-strikes");
let mut fields: Vec<&str> = arm["properties"]
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.filter(|k| *k != "type")
.collect();
fields.sort();
assert!(fields.starts_with(&["assembly"]), "{fields:?}");
let hold = &s["$defs"]["StrikeStep"]["properties"]["hold"];
assert_eq!(hold["type"], "integer", "{hold}");
assert!(hold.get("maximum").is_none(), "{hold}");
}
#[test]
fn the_trigger_union_gains_strike_assembly() {
let s = quests();
let kinds = tags(&s, "TriggerOn", "on");
assert_eq!(
kinds,
[
"strike",
"use",
"approach",
"step",
"strike-npc",
"strike-assembly"
]
);
let b = branch(&s, "TriggerOn", "on", "strike-assembly");
let mut fields: Vec<&str> = b["properties"]
.as_object()
.unwrap()
.keys()
.map(String::as_str)
.collect();
fields.sort();
assert_eq!(fields, ["assembly", "on"]);
}
#[test]
fn at_the_manifest_version() {
let manifest = include_str!("../Cargo.toml");
let version = manifest
.lines()
.find_map(|l| l.strip_prefix("version = \""))
.and_then(|v| v.strip_suffix('"'))
.expect("the manifest states a version");
assert_eq!(DSL_VERSION, version);
}