131 lines
4.7 KiB
Rust
131 lines
4.7 KiB
Rust
pub mod config;
|
|
pub mod describe;
|
|
pub mod levels;
|
|
pub mod progress;
|
|
pub mod similarity;
|
|
pub mod tui;
|
|
|
|
#[cfg(test)]
|
|
mod smoke {
|
|
//! End-to-end wiring test with one hardcoded level (no `Level` trait yet).
|
|
|
|
use super::*;
|
|
use serde::Serialize;
|
|
use serde_yaml::{Mapping, Value};
|
|
|
|
#[derive(Serialize)]
|
|
struct JunctionCtx {
|
|
directions: Vec<Direction>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct Direction {
|
|
name: &'static str,
|
|
feature: &'static str,
|
|
}
|
|
|
|
#[test]
|
|
fn one_level_round_trip() {
|
|
// (1) Describer renders prose for a fake "junction" level.
|
|
let mut d = describe::Describer::new();
|
|
d.register(
|
|
"junction",
|
|
"You stand at a junction:\n\
|
|
{% for x in directions %}- {{ x.name }} leads to a {{ x.feature }}\n{% endfor %}",
|
|
)
|
|
.unwrap();
|
|
let prose = d
|
|
.render(
|
|
"junction",
|
|
&JunctionCtx {
|
|
directions: vec![
|
|
Direction { name: "left", feature: "door" },
|
|
Direction { name: "right", feature: "tunnel" },
|
|
],
|
|
},
|
|
)
|
|
.unwrap();
|
|
assert!(prose.contains("left leads to a door"));
|
|
assert!(prose.contains("right leads to a tunnel"));
|
|
|
|
// (2) Canonical target via serde_yaml — what a generator will do.
|
|
// A reordered candidate parses to the same Value, so the semantic
|
|
// short-circuit must score it 1.0.
|
|
let mut m = Mapping::new();
|
|
m.insert(Value::String("left".into()), Value::String("door".into()));
|
|
m.insert(Value::String("right".into()), Value::String("tunnel".into()));
|
|
let target = serde_yaml::to_string(&Value::Mapping(m)).unwrap();
|
|
let candidate = "right: tunnel\nleft: door\n";
|
|
assert_eq!(
|
|
similarity::semantic_or_textual(&target, candidate),
|
|
1.0,
|
|
"reordered keys should still be a perfect semantic match"
|
|
);
|
|
|
|
// (3) Textually-different, semantically-different → ratio in (0, 1).
|
|
let near_miss = similarity::similarity_ratio("a: 1\nb: 2\n", "a: 1\nb: 3\n");
|
|
assert!(near_miss > 0.0 && near_miss < 1.0);
|
|
|
|
// (4) Progress round-trips through the same YAML pipeline that disk
|
|
// save/load will use.
|
|
let p = progress::Progress {
|
|
nuggets: vec![(1, levels::Nugget::Silver), (2, levels::Nugget::Gold)],
|
|
current_level: 3,
|
|
current_seed: 0xCAFE,
|
|
attempts: 1,
|
|
};
|
|
let s = serde_yaml::to_string(&p).unwrap();
|
|
let loaded: progress::Progress = serde_yaml::from_str(&s).unwrap();
|
|
assert_eq!(loaded.nuggets, p.nuggets);
|
|
assert_eq!(loaded.current_level, p.current_level);
|
|
assert_eq!(loaded.current_seed, p.current_seed);
|
|
}
|
|
|
|
#[test]
|
|
fn levels_generate_canonical_yaml() {
|
|
let registry = levels::registry();
|
|
assert_eq!(registry.len(), 3);
|
|
|
|
// Level 1: any null-equivalent passes via the semantic short-circuit.
|
|
let g1 = registry[0].generate(0);
|
|
let parsed: serde_yaml::Value = serde_yaml::from_str(&g1.target_yaml).unwrap();
|
|
assert!(parsed.is_null());
|
|
assert_eq!(
|
|
similarity::semantic_or_textual(&g1.target_yaml, "---"),
|
|
1.0,
|
|
"`---` should be accepted as the minimum YAML"
|
|
);
|
|
assert_eq!(
|
|
similarity::semantic_or_textual(&g1.target_yaml, "null"),
|
|
1.0
|
|
);
|
|
|
|
// Level 2: deterministic per seed, non-empty mapping.
|
|
let g2 = registry[1].generate(42);
|
|
let v2: serde_yaml::Value = serde_yaml::from_str(&g2.target_yaml).unwrap();
|
|
let m = v2.as_mapping().expect("level 2 produces a mapping");
|
|
assert!(!m.is_empty());
|
|
let g2_again = registry[1].generate(42);
|
|
assert_eq!(
|
|
g2.target_yaml, g2_again.target_yaml,
|
|
"same seed should produce the same target"
|
|
);
|
|
|
|
// Level 3: deterministic per seed; produces a mapping of mappings,
|
|
// each inner mapping has a `type` key.
|
|
let g3 = registry[2].generate(123);
|
|
let v3: serde_yaml::Value = serde_yaml::from_str(&g3.target_yaml).unwrap();
|
|
let m3 = v3.as_mapping().expect("level 3 produces a mapping");
|
|
assert!(!m3.is_empty());
|
|
for (_dir, feature) in m3 {
|
|
let inner = feature.as_mapping().expect("level 3 inner is a mapping");
|
|
assert!(
|
|
inner.get(serde_yaml::Value::String("type".into())).is_some(),
|
|
"each direction must carry a `type` key"
|
|
);
|
|
}
|
|
let g3_again = registry[2].generate(123);
|
|
assert_eq!(g3.target_yaml, g3_again.target_yaml);
|
|
}
|
|
}
|