mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-09 20:10:12 -06:00
Menus: test that every menu follows the reference menu tree's order
Reads xtask/data/menu-catalog.txt (the parity oracle) into a tree and checks menus::tree against it at every level. Fails on main: 19 menus and submenus list their items in command registration order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b6331c8603
commit
e8e76d2e60
@@ -8,6 +8,43 @@ use crate::CadApp;
|
||||
/// The top-level menus, in order.
|
||||
pub const MENUS: &[&str] = &["File", "Edit", "View", "Insert", "Format", "Tools", "Draw", "Dimension", "Modify", "Window", "Help"];
|
||||
|
||||
/// The reference menu tree (feature names, four spaces per level): the order every menu lists
|
||||
/// its items in. The same file is `cargo xtask parity`'s oracle.
|
||||
const REFERENCE_TREE: &str = include_str!("../../../xtask/data/menu-catalog.txt");
|
||||
|
||||
/// A menu of the reference tree: its label and its items, in order.
|
||||
struct Reference {
|
||||
label: &'static str,
|
||||
children: Vec<Reference>,
|
||||
}
|
||||
|
||||
/// The reference tree's top-level menus.
|
||||
fn reference() -> &'static [Reference] {
|
||||
static TREE: std::sync::OnceLock<Vec<Reference>> = std::sync::OnceLock::new();
|
||||
TREE.get_or_init(|| {
|
||||
// The menus being read, outermost first; a line closes every menu deeper than its indent.
|
||||
let mut open = vec![Reference { label: "", children: Vec::new() }];
|
||||
let close = |open: &mut Vec<Reference>| {
|
||||
if let Some(done) = open.pop()
|
||||
&& let Some(parent) = open.last_mut()
|
||||
{
|
||||
parent.children.push(done);
|
||||
}
|
||||
};
|
||||
for line in REFERENCE_TREE.lines().filter(|l| !l.trim().is_empty()) {
|
||||
let depth = (line.len() - line.trim_start_matches(' ').len()) / 4;
|
||||
while open.len() > depth + 1 {
|
||||
close(&mut open);
|
||||
}
|
||||
open.push(Reference { label: line.trim(), children: Vec::new() });
|
||||
}
|
||||
while open.len() > 1 {
|
||||
close(&mut open);
|
||||
}
|
||||
open.pop().map(|root| root.children).unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
/// File ▸ Open Recent: one `ui.recent.<n>` item per recent file, then Clear Recent Files.
|
||||
pub const OPEN_RECENT: &str = "Open Recent";
|
||||
const RECENT_ITEM: &str = "ui.recent.";
|
||||
@@ -993,6 +1030,70 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod reference_order_tests {
|
||||
use cadcraft_engine::Session;
|
||||
|
||||
use super::{Entry, Reference, reference, tree};
|
||||
use crate::{CadApp, Services};
|
||||
|
||||
fn label(e: &Entry) -> &str {
|
||||
match e {
|
||||
Entry::Item { label, .. } | Entry::Sub { label, .. } => label,
|
||||
}
|
||||
}
|
||||
|
||||
/// Every item the reference lists comes in the reference's order, at every level, and the
|
||||
/// items the reference lacks follow them.
|
||||
fn check(path: &str, entries: &[Entry], reference: &[Reference], failures: &mut Vec<String>) {
|
||||
let known = |e: &Entry| reference.iter().any(|r| r.label == label(e));
|
||||
let listed: Vec<&str> = entries.iter().filter(|e| known(e)).map(label).collect();
|
||||
let expected: Vec<&str> = reference.iter().map(|r| r.label).filter(|l| listed.contains(l)).collect();
|
||||
if listed != expected {
|
||||
failures.push(format!("{path}: {listed:?} should be {expected:?}"));
|
||||
}
|
||||
if let (Some(unlisted), Some(last)) = (entries.iter().position(|e| !known(e)), entries.iter().rposition(known))
|
||||
&& unlisted < last
|
||||
{
|
||||
failures.push(format!("{path}: {} comes before the reference's items", entries.get(unlisted).map_or("?", label)));
|
||||
}
|
||||
for e in entries {
|
||||
if let Entry::Sub { label, children } = e
|
||||
&& let Some(r) = reference.iter().find(|r| r.label == *label)
|
||||
{
|
||||
check(&format!("{path} > {label}"), children, &r.children, failures);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn menus_follow_the_reference_tree() {
|
||||
let app = CadApp::new(Session::new(), Services::default());
|
||||
let mut failures = Vec::new();
|
||||
for (menu, entries) in tree(&app) {
|
||||
let r = reference().iter().find(|r| r.label == menu).unwrap_or_else(|| panic!("{menu} is not in the reference tree"));
|
||||
check(&menu, &entries, &r.children, &mut failures);
|
||||
}
|
||||
assert!(failures.is_empty(), "{}", failures.join("\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_tree_is_nested_by_indentation() {
|
||||
assert_eq!(reference().iter().map(|r| r.label).collect::<Vec<_>>(), super::MENUS);
|
||||
let draw = reference().iter().find(|r| r.label == "Draw").unwrap();
|
||||
let circle = draw.children.iter().find(|r| r.label == "Circle").unwrap();
|
||||
assert_eq!(circle.children.first().map(|r| r.label), Some("Center, Radius"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_recent_stays_after_open() {
|
||||
let app = CadApp::new(Session::new(), Services::default());
|
||||
let file = tree(&app).into_iter().find(|(m, _)| m == "File").map(|(_, e)| e).unwrap_or_default();
|
||||
let open = file.iter().position(|e| matches!(e, Entry::Item { id, .. } if id == "open")).unwrap();
|
||||
assert_eq!(file.get(open + 1).map(label), Some(super::OPEN_RECENT));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod shortcut_label_tests {
|
||||
use super::shortcut_label;
|
||||
|
||||
Reference in New Issue
Block a user