Spezifikationsabgleich und Regressionsnachweise abschließen

This commit is contained in:
2026-09-06 10:31:16 +02:00
parent c8b92f0619
commit 024336e29c
39 changed files with 2485 additions and 120 deletions

View File

@@ -84,8 +84,9 @@ impl FormFile {
) -> Result<Vec<FormInitial>, tb_runtime::errors::RuntimeError> {
fn collect(
node: &FormNode,
parent: Option<u16>,
parent: Option<(u16, Option<i32>)>,
catalog: &FormCatalog,
form: &str,
out: &mut Vec<FormInitial>,
depth: usize,
) -> Result<(), tb_runtime::errors::RuntimeError> {
@@ -98,7 +99,8 @@ impl FormFile {
.iter()
.position(|o| {
o.name.eq_ignore_ascii_case(&node.name)
&& o.parent == parent
&& ((o.array && catalog.belongs_to(o, form))
|| o.parent == parent.map(|p| p.0))
&& o.class == node.class
})
.ok_or(tb_runtime::errors::RuntimeError(420))? as u16;
@@ -115,18 +117,29 @@ impl FormFile {
if out.iter().any(|v| v.object == object && v.index == index) {
return Err(tb_runtime::errors::RuntimeError(5));
}
let mut properties = node.properties.clone();
if let Some((id, _)) = forms::property(node.class, "PARENT") {
properties.insert(id, PropertyValue::Object(parent));
}
out.push(FormInitial {
object,
index,
properties: node.properties.clone(),
properties,
});
for child in &node.children {
collect(child, Some(object), catalog, out, depth)?;
collect(
child,
Some((object, (index != 0).then_some(index))),
catalog,
form,
out,
depth,
)?;
}
Ok(())
}
let mut values = Vec::new();
collect(&self.root, None, catalog, &mut values, 0)?;
collect(&self.root, None, catalog, &self.root.name, &mut values, 0)?;
Ok(values)
}
@@ -941,7 +954,7 @@ fn decode_object(symbol: &BinarySymbol, record: &BinaryRecord, bytes: &[u8]) ->
set_property(&mut node, name, PropertyValue::Integer(value as i32));
}
}
if let Some(value) = record_u16(bytes, start, end, 4) {
if let Some(value) = record_u16(bytes, start, end, 4).filter(|_| symbol.is_array) {
set_property(
&mut node,
"INDEX",
@@ -1314,7 +1327,7 @@ fn write_node(node: &FormNode, depth: usize, out: &mut String) {
let Some(value) = node.properties.get(&(id as u16)) else {
continue;
};
if *value == default_value(spec) {
if spec.name != "INDEX" && *value == default_value(spec) {
continue;
}
out.push_str(&" ".repeat(depth + 1));
@@ -1437,6 +1450,54 @@ mod tests {
const EXAMPLE: &str = "VERSION 1.00\nBegin Form Form1\n Caption = \"Beispiel\"\n Height = 15\n Begin CommandButton cmdOK\n Caption = \"&OK\"\n End\nEnd\n\nSUB cmdOK_Click ()\n UNLOAD Form1\nEND SUB\n";
#[test]
fn explicit_default_is_preserved_until_canonical_write() {
let original = "VERSION 1.00\r\nBegin Form Form1\r\n Begin CommandButton Ok\r\n Enabled = -1\r\n End\r\nEnd\r\n";
let mut form = read_text("default.frm", original).unwrap();
assert_eq!(write_text(&form), original);
form.root.name = "Changed".into();
let canonical = write_text(&form);
assert_eq!(
canonical,
"VERSION 1.00\nBegin Form Changed\n Begin CommandButton Ok\n End\nEnd\n"
);
assert_eq!(
write_text(&read_text("canonical.frm", &canonical).unwrap()),
canonical
);
}
#[test]
fn canonical_index_zero_remains_an_array_and_array_parents_are_preserved() {
let mut form=read_text("array.frm", "VERSION 1.00\nBegin Form Form1\n Begin Frame A\n Begin Label Item\n Index = 0\n End\n End\n Begin Frame B\n Begin Label Item\n Index = 1\n End\n End\nEnd\n").unwrap();
form.code = "END\n".into();
let text = write_text(&form);
assert!(text.contains("Index = 0"));
let roundtrip = read_text("roundtrip.frm", &text).unwrap();
let catalog = roundtrip.catalog();
let item = catalog.find("Item").unwrap().0;
assert!(catalog.objects[item as usize].array);
let a = catalog.find("A").unwrap().0;
let b = catalog.find("B").unwrap().0;
let mut model = FormsModel::new(catalog.objects, 80, 25);
roundtrip.apply(&mut model).unwrap();
let parent = forms::property(ObjectClass::Label, "PARENT").unwrap().0;
assert_eq!(
model.get_at(item, Some(0), parent).unwrap(),
PropertyValue::Object(Some((a, None)))
);
assert_eq!(
model.get_at(item, Some(1), parent).unwrap(),
PropertyValue::Object(Some((b, None)))
);
form.root.children.pop();
let single = read_text("single.frm", &write_text(&form)).unwrap();
assert!(
single.catalog().objects[item as usize].array,
"Einzelelement mit Index 0 bleibt Array"
);
}
#[test]
fn reads_nested_form_and_preserves_source_exactly() {
let form = read_text("test.frm", EXAMPLE).unwrap();