use pathland_core::{
component_type, init_memory, property_id, value_type, Guest, Host, MemoryLayout, Opcode,
APPEND,
};
struct Memory {
mem: Vec<u8>,
layout: MemoryLayout,
}
impl Memory {
fn new(layout: MemoryLayout) -> Self {
let mut mem = vec![0u8; layout.total_bytes()];
init_memory(&mut mem, &layout);
Self { mem, layout }
}
fn guest(&mut self) -> Guest<'_> {
Guest::new(&mut self.mem, &self.layout)
}
fn host(&mut self) -> Host<'_> {
Host::new(&mut self.mem, &self.layout)
}
}
#[test]
fn multiple_frames_coalesce_into_one() {
let mut m = Memory::new(MemoryLayout::default());
{
let mut g = m.guest();
g.begin_frame();
g.create_node(1, component_type::VSTACK).unwrap();
g.create_node(2, component_type::HSTACK).unwrap();
g.end_frame();
assert_eq!(g.frame_count(), 1);
}
{
let mut g = m.guest();
g.begin_frame();
g.create_node(3, component_type::TEXT).unwrap();
g.end_frame();
assert_eq!(g.frame_count(), 2);
}
let mut h = m.host();
let frames = h.frames();
assert_eq!(frames.len(), 1);
let ops: Vec<Opcode> = frames[0].opcodes().collect();
assert_eq!(ops.len(), 3);
assert_eq!(ops[0].a(), 1);
assert_eq!(ops[1].a(), 2);
assert_eq!(ops[2].a(), 3);
assert!(h.frames().is_empty());
}
#[test]
fn fresh_host_after_another_host_drained_returns_empty() {
let mut m = Memory::new(MemoryLayout::default());
{
let mut g = m.guest();
g.begin_frame();
g.create_node(1, component_type::VSTACK).unwrap();
g.end_frame();
}
{
let mut h1 = m.host();
assert_eq!(h1.frames().len(), 1);
}
let mut h2 = m.host();
assert!(h2.frames().is_empty());
}
#[test]
fn pending_counts_opcodes_since_begin_frame() {
let mut m = Memory::new(MemoryLayout::default());
{
let mut g = m.guest();
g.begin_frame();
assert_eq!(g.pending(), 0);
g.create_node(1, component_type::VSTACK).unwrap();
g.create_node(2, component_type::TEXT).unwrap();
assert_eq!(g.pending(), 2);
g.end_frame();
}
{
let mut g = m.guest();
g.begin_frame();
assert_eq!(g.pending(), 0);
g.create_node(3, component_type::HSTACK).unwrap();
assert_eq!(g.pending(), 1);
g.end_frame();
}
}
#[test]
fn frame_count_is_monotonic() {
let mut m = Memory::new(MemoryLayout::default());
let mut g = m.guest();
assert_eq!(g.frame_count(), 0);
for expected in 1..=5 {
g.begin_frame();
g.create_node(expected, component_type::VSTACK).unwrap();
g.end_frame();
assert_eq!(g.frame_count(), expected);
}
}
#[test]
fn all_value_types_encode_into_b_field() {
let mut m = Memory::new(MemoryLayout::default());
let mut g = m.guest();
g.begin_frame();
let cases: &[(u8, u16, u32)] = &[
(value_type::U8, property_id::LINE_LIMIT, 1),
(value_type::U32, property_id::Z_INDEX, 42),
(value_type::I32, property_id::PADDING_TOP, 7),
(value_type::F32, property_id::SPACING, 4.0f32.to_bits()),
(value_type::STRING, property_id::FONT_FAMILY, 0),
(value_type::ENUM, property_id::ALIGNMENT, 1),
(value_type::COLOR, property_id::COLOR, 0xFF00_00FF),
(value_type::DESIGN_TOKEN, property_id::PADDING, 0),
];
for (vt, pid, value) in cases {
g.set_property(1, *pid, *vt, *value).unwrap();
}
g.end_frame();
let mut h = m.host();
let ops: Vec<Opcode> = h.frames()[0].opcodes().collect();
assert_eq!(ops.len(), cases.len());
for (op, (vt, pid, _)) in ops.iter().zip(cases) {
assert_eq!(op.category(), pathland_core::category::PARAMETER);
assert_eq!(op.command(), pathland_core::parameter::SET_PROPERTY);
assert_eq!(op.b(), (*vt as u32) << 16 | *pid as u32);
}
}
#[test]
fn insert_child_with_explicit_index_round_trips() {
let mut m = Memory::new(MemoryLayout::default());
{
let mut g = m.guest();
g.begin_frame();
g.create_node(1, component_type::VSTACK).unwrap();
g.create_node(2, component_type::TEXT).unwrap();
g.create_node(3, component_type::TEXT).unwrap();
g.insert_child(1, 2, APPEND).unwrap();
g.insert_child(1, 3, 0).unwrap();
g.end_frame();
}
let mut h = m.host();
let ops: Vec<Opcode> = h.frames()[0].opcodes().collect();
assert_eq!(ops[3].command(), pathland_core::tree::INSERT_CHILD);
assert_eq!(ops[3].c(), APPEND);
assert_eq!(ops[4].command(), pathland_core::tree::INSERT_CHILD);
assert_eq!(ops[4].c(), 0);
}