use chappi_core::{
BudgetUnit, CapsuleBudget, CognitiveState, ProjectionFormat, build_capsule, project,
snapshot_source, state_hash, validate,
};
use chappi_syntax::parse_document;
fn decode(source: &str) -> CognitiveState {
CognitiveState::try_from(parse_document(source).expect("fixture syntax should parse"))
.expect("fixture semantics should decode")
}
#[test]
fn state_hash_is_stable_across_source_formatting_and_semantic_order() {
let ordered = decode(
r#"(chappi 1)
(entity self self persistent 100 person (name "Self"))
(claim c self working 70 likely (text "same"))
(rel about c self)
(focus thought 0 self evoked none (fg c) (bg self) (residue) (trigger start))
"#,
);
let reordered = decode(
r#"; different spans and top-level order
(focus thought 0 self evoked none (fg c) (bg self) (residue) (trigger start))
(rel about c self)
(claim c self working 070 likely (text "same"))
(chappi 1)
(entity self self persistent 100 person (name "Self"))
"#,
);
assert_eq!(state_hash(&ordered), state_hash(&reordered));
}
#[test]
fn state_hash_includes_node_revisions_and_focus_streams() {
let state = decode(
r#"(chappi 1)
(entity self self persistent 100 person (self))
(claim c self working 70 likely (claim))
"#,
);
let mut revised = state.clone();
revised.nodes[1].revision = 1;
let with_focus = decode(
r#"(chappi 1)
(entity self self persistent 100 person (self))
(claim c self working 70 likely (claim))
(focus thought 0 self evoked none (fg c) (bg) (residue) (trigger start))
"#,
);
assert_ne!(state_hash(&state), state_hash(&revised));
assert_ne!(state_hash(&state), state_hash(&with_focus));
}
#[test]
fn state_hash_normalizes_canonically_equivalent_unicode_symbols() {
let composed = decode("(chappi 1)\n(entity café café persistent 100 person (self))\n");
let decomposed =
decode("(chappi 1)\n(entity cafe\u{301} cafe\u{301} persistent 100 person (self))\n");
assert_eq!(state_hash(&composed), state_hash(&decomposed));
}
#[test]
fn focus_buckets_are_semantic_sets_in_equality_and_canonical_source() {
let left = decode(
r#"(chappi 1)
(entity self self persistent 100 person (self))
(claim a self working 70 likely (a))
(claim b self working 60 likely (b))
(focus thought 0 self evoked none (fg b a) (bg self) (residue) (trigger start))
"#,
);
let right = decode(
r#"(chappi 1)
(entity self self persistent 100 person (self))
(claim a self working 70 likely (a))
(claim b self working 60 likely (b))
(focus thought 0 self evoked none (fg a b) (bg self) (residue) (trigger start))
"#,
);
assert!(validate(&left).is_empty());
assert!(validate(&right).is_empty());
assert_eq!(state_hash(&left), state_hash(&right));
assert_eq!(left, right);
assert_eq!(snapshot_source(&left), snapshot_source(&right));
let mut programmatic = right.clone();
programmatic.focus_streams[0].frames[0].foreground.reverse();
assert_eq!(programmatic, right);
assert_eq!(snapshot_source(&programmatic), snapshot_source(&right));
assert_eq!(
project(&programmatic, ProjectionFormat::Json),
project(&right, ProjectionFormat::Json)
);
let budget = CapsuleBudget {
limit: 10_000,
unit: BudgetUnit::Bytes,
};
assert_eq!(
build_capsule(&programmatic, budget).canonical_content(),
build_capsule(&right, budget).canonical_content()
);
assert!(
std::str::from_utf8(&snapshot_source(&left))
.unwrap()
.contains("(fg a b)")
);
}