User scenarios in the real UI, headless: scenario::Harness draws the app frame by frame into an egui context and drives it with control-channel input (toolbar starts, clicks on world/sketch/origin-plane/axis points, keys, typing, drags) and checks the model; tests/scenarios/*.json: box with fillets, shell and hole; revolve on XZ; sketch on a face and cut; mirror; delete and undo. Control: ui.at, ui.dialogInput. Fixes found by them: a shape started on an existing point (the origin, a corner) is tied to it; the Dimension tool's value is edited in place with the value selected, so typing replaces it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 09:32:49 +00:00
1 parent 62dbf60eb2
commit d5b8aa0375
12 files changed
+802 -6

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
//! User scenarios in the real UI, headless (see `solvecraft_ui_egui::scenario`). Each JSON file
//! under `tests/scenarios/` is one workflow done with clicks, keys and drags.
use solvecraft_ui_egui::scenario::Harness;
fn run(name: &str) {
let path = format!("{}/tests/scenarios/{name}.json", env!("CARGO_MANIFEST_DIR"));
let text = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{path}: {e}"));
let steps: serde_json::Value = serde_json::from_str(&text).unwrap_or_else(|e| panic!("{path}: {e}"));
let mut h = Harness::new();
if let Err(e) = h.run(&steps) {
panic!("{name}: {e}");
}
}
macro_rules! scenarios {
($($name:ident),* $(,)?) => {
$(#[test]
fn $name() {
run(stringify!($name));
})*
};
}
scenarios!(s01_box_fillet_hole_shell, s02_revolve_on_xz, s03_sketch_on_face_cut, s04_mirror_body, s05_delete_and_undo);
@@ -0,0 +1,206 @@
[
{
"start": "SketchCreate"
},
{
"click": {
"plane": "XY"
}
},
{
"expect": {
"sketching": true
}
},
{
"start": "ShapeRectangleTwoPoint"
},
{
"click": {
"sketch": [
0,
0
]
}
},
{
"click": {
"sketch": [
40,
30
]
}
},
{
"key": "Escape"
},
{
"expect": {
"dof": 2
}
},
{
"start": "SketchDimension"
},
{
"click": {
"sketch": [
20,
0
]
}
},
{
"text": "50"
},
{
"key": "Enter"
},
{
"key": "Escape"
},
{
"expect": {
"dialog": null,
"param": {
"d1": 50
}
}
},
{
"shot": "s01_sketch"
},
{
"call": "ui.view",
"params": {
"view": "iso"
}
},
{
"start": "SketchStop"
},
{
"expect": {
"sketching": false
}
},
{
"start": "Extrude"
},
{
"text": "20"
},
{
"key": "Enter"
},
{
"expect": {
"bodies": 1,
"volume": 30000,
"dialog": null
}
},
{
"shot": "s01_extrude"
},
{
"start": "FusionFilletEdgesCommand"
},
{
"click": {
"world": [
50,
15,
20
]
}
},
{
"click": {
"world": [
0,
15,
20
]
}
},
{
"text": "3"
},
{
"key": "Enter"
},
{
"expect": {
"bodies": 1,
"errors": 0,
"dialog": null,
"volume": [
29884.1,
1e-05
]
}
},
{
"shot": "s01_fillet"
},
{
"start": "FusionShellBodyCommand"
},
{
"click": {
"world": [
25,
0,
10
]
}
},
{
"text": "2"
},
{
"key": "Enter"
},
{
"expect": {
"bodies": 1,
"errors": 0,
"dialog": null
}
},
{
"shot": "s01_shell"
},
{
"start": "FusionHoleCommand"
},
{
"click": {
"world": [
25,
15,
20
]
}
},
{
"text": "8"
},
{
"key": "Enter"
},
{
"expect": {
"bodies": 1,
"errors": 0,
"dialog": null
}
},
{
"debug": 1
},
{
"shot": "s01_hole"
}
]
@@ -0,0 +1,18 @@
[
{"start": "SketchCreate"},
{"click": {"plane": "XZ"}},
{"expect": {"sketching": true}},
{"start": "ShapeRectangleTwoPoint"},
{"click": {"sketch": [10, 0]}},
{"click": {"sketch": [20, 30]}},
{"key": "Escape"},
{"shot": "s02_sketch"},
{"start": "SketchStop"},
{"call": "ui.view", "params": {"view": "iso"}},
{"start": "Revolve"},
{"click": {"axis": "Z"}},
{"debug": 1},
{"key": "Enter"},
{"expect": {"bodies": 1, "errors": 0, "dialog": null, "volume": [28274.33, 1e-3]}},
{"shot": "s02_revolve"}
]
@@ -0,0 +1,30 @@
[
{"start": "SketchCreate"},
{"click": {"plane": "XY"}},
{"start": "ShapeRectangleTwoPoint"},
{"click": {"sketch": [0, 0]}},
{"click": {"sketch": [40, 30]}},
{"key": "Escape"},
{"start": "SketchStop"},
{"call": "ui.view", "params": {"view": "iso"}},
{"start": "Extrude"},
{"text": "20"},
{"key": "Enter"},
{"expect": {"bodies": 1, "volume": 24000}},
{"start": "SketchCreate"},
{"click": {"world": [20, 15, 20]}},
{"expect": {"sketching": true}},
{"start": "CircleCenterRadius"},
{"click": {"sketch": [20, 15]}},
{"click": {"sketch": [25, 15]}},
{"key": "Escape"},
{"shot": "s03_sketch_on_face"},
{"start": "SketchStop"},
{"start": "Extrude"},
{"click": {"world": [20, 15, 20]}},
{"debug": 1},
{"text": "-10"},
{"key": "Enter"},
{"expect": {"bodies": 1, "errors": 0, "dialog": null, "volume": [23214.6, 1e-4]}},
{"shot": "s03_cut"}
]
@@ -0,0 +1,22 @@
[
{"start": "SketchCreate"},
{"click": {"plane": "XY"}},
{"start": "CircleCenterRadius"},
{"click": {"sketch": [20, 10]}},
{"click": {"sketch": [25, 10]}},
{"key": "Escape"},
{"start": "SketchStop"},
{"call": "ui.view", "params": {"view": "iso"}},
{"start": "Extrude"},
{"text": "10"},
{"key": "Enter"},
{"expect": {"bodies": 1}},
{"start": "MirrorCommand"},
{"click": {"world": [20, 10, 10]}},
{"call": "ui.dialogInput", "params": {"index": 1}},
{"click": {"plane": "YZ"}},
{"debug": 1},
{"key": "Enter"},
{"expect": {"bodies": 2, "errors": 0, "dialog": null, "volume": [1570.8, 1e-3]}},
{"shot": "s04_mirror"}
]
@@ -0,0 +1,131 @@
[
{
"start": "SketchCreate"
},
{
"click": {
"plane": "XY"
}
},
{
"start": "ShapeRectangleTwoPoint"
},
{
"click": {
"sketch": [
0,
0
]
}
},
{
"click": {
"sketch": [
30,
20
]
}
},
{
"key": "Escape"
},
{
"start": "SketchStop"
},
{
"call": "ui.view",
"params": {
"view": "iso"
}
},
{
"start": "Extrude"
},
{
"text": "10"
},
{
"key": "Enter"
},
{
"expect": {
"bodies": 1,
"volume": 6000
}
},
{
"click": {
"world": [
15,
10,
10
]
}
},
{
"expect": {
"selection": 1
}
},
{
"expect": {
"selection": 1
}
},
{
"call": "ui.menu",
"params": {
"x": 100,
"y": 260,
"target": {
"type": "body",
"name": "Body1"
}
}
},
{
"call": "ui.menuPick",
"params": {
"item": "Delete"
}
},
{
"debug": 1
},
{
"expect": {
"bodies": 0
}
},
{
"key": "Z",
"cmd": true
},
{
"expect": {
"bodies": 1,
"volume": 6000
}
},
{
"key": "Y",
"cmd": true
},
{
"expect": {
"bodies": 0
}
},
{
"key": "Z",
"cmd": true
},
{
"expect": {
"bodies": 1
}
},
{
"shot": "s05_undone"
}
]