#![cfg(all(feature = "desktop", not(alloc_frugal)))]
use rust_widgets::designer::{generate, GenerationRequest, TargetProfile};
use rust_widgets::json::{JsonLoader, JsonProject};
const PROJECT: &str = r#"{
"window": {
"id": "root",
"title": "Consistency",
"width": 800,
"height": 600,
"layout": {
"type": "vbox",
"children": [
{ "label": { "id": "heading", "text": "Settings" } },
{ "button": { "id": "save", "text": "Save", "enabled": true,
"events": { "clicked": "on_save" } } },
{ "slider": { "id": "volume", "value": 40 } }
]
}
}
}"#;
fn request(target: TargetProfile) -> GenerationRequest {
GenerationRequest {
json: String::from(PROJECT),
target,
width: 800,
height: 600,
function_name: String::from("build_ui"),
}
}
fn mode1_structure() -> Vec<(String, Vec<usize>)> {
let project = JsonProject::parse(PROJECT).expect("the document must parse");
project.walk().map(|node| (node.widget.clone(), node.path.clone())).collect()
}
fn mode2_nodes(source: &str) -> Vec<String> {
source
.split("Node::new(")
.skip(1)
.filter_map(|rest| {
let rest = rest.strip_prefix('"')?;
rest.split('"').next().map(String::from)
})
.collect()
}
fn mode2_stripped_nodes(source: &str) -> Vec<String> {
source
.lines()
.map(str::trim)
.filter(|line| line.starts_with("let ") && line.contains("::new("))
.filter_map(|line| {
let after = line.split("::new(").next()?;
let type_name = after.rsplit(|c: char| !(c.is_alphanumeric() || c == '_')).next()?;
if type_name.is_empty() || type_name == "Rect" {
return None;
}
Some(String::from(type_name))
})
.collect()
}
#[test]
fn the_stripped_template_agrees_on_the_control_tree() {
let mode1 = mode1_structure();
let generated = generate(&request(TargetProfile::Stripped)).expect("generation must succeed");
let mode2 = mode2_stripped_nodes(&generated.source);
let expected: Vec<String> = mode1
.iter()
.map(|(widget, _)| rust_widgets::designer::generator::constructor_type_name(widget))
.map(String::from)
.collect();
assert_eq!(
expected, mode2,
"the stripped template must describe the same controls in the same order as mode 1.\n\
mode 2 source:\n{}",
generated.source
);
assert_eq!(
generated.report.nodes_emitted,
mode1.len(),
"the report's node count must match the tree it emitted"
);
}
#[test]
fn both_modes_agree_on_the_control_tree() {
let mode1 = mode1_structure();
let generated = generate(&request(TargetProfile::Default)).expect("generation must succeed");
let mode2 = mode2_nodes(&generated.source);
let mode1_names: Vec<String> = mode1.iter().map(|(widget, _)| widget.clone()).collect();
assert_eq!(
mode1_names, mode2,
"mode 1 and mode 2 must describe the same controls in the same order.\n\
mode 2 source:\n{}",
generated.source
);
assert_eq!(
generated.report.nodes_emitted,
mode1.len(),
"the report's node count must match the tree it emitted"
);
}
#[test]
fn both_modes_agree_on_property_values() {
let project = JsonProject::parse(PROJECT).expect("the document must parse");
let generated = generate(&request(TargetProfile::Default)).expect("generation must succeed");
let mut checked = 0usize;
for node in project.walk() {
for (name, value) in node.scalar_properties() {
let expected_name = format!(".prop(\"{name}\"");
assert!(
generated.source.contains(&expected_name),
"`{name}` on {:?} is in the document but not in the generated tree",
node.path
);
if let Some(bool_value) = value.as_bool() {
assert!(
generated.source.contains(&format!("CapabilityValue::Bool({bool_value})")),
"`{name}` on {:?} must be emitted with its value",
node.path
);
}
if let Some(number) = value.as_i64() {
assert!(
generated.source.contains(&format!("({number})")),
"`{name}` on {:?} must be emitted with its value",
node.path
);
}
if let Some(text) = value.as_str() {
assert!(
generated.source.contains(text),
"`{name}` on {:?} must be emitted with its text",
node.path
);
}
checked += 1;
}
}
assert!(checked >= 3, "the fixture must exercise several properties (got {checked})");
}
#[test]
fn both_modes_agree_on_the_declared_handlers() {
let project = JsonProject::parse(PROJECT).expect("the document must parse");
let factory = rust_widgets::widget::capability::WidgetFactory::new_with_defaults();
let mut declared = 0usize;
for node in project.walk() {
for (event, handler) in node.declared_handlers() {
assert!(
factory
.event_is_subscribable(
&node.widget,
&event,
&rust_widgets::signal::CustomSignalHub::new()
)
.is_ok(),
"`{}` declares `events.{event}`, which its capability does not publish; the \
handler `{handler}` could never run",
node.widget
);
declared += 1;
}
}
assert_eq!(declared, 1, "the fixture declares exactly one wire");
let layout = JsonLoader::load(PROJECT).expect("mode 1 must accept the document");
assert!(
layout.id("save").is_some(),
"the control carrying the wire must be registered by mode 1"
);
}
#[test]
fn the_stripped_output_names_nothing_the_stripped_profile_lacks() {
let generated = generate(&request(TargetProfile::Stripped)).expect("generation must succeed");
let source = &generated.source;
for forbidden in [
"rust_widgets::view",
"rust_widgets::json",
"create_button",
"create_label",
"create_slider",
"widget::runtime",
"WidgetFactory",
"JsonLoader",
] {
assert!(
!source.contains(forbidden),
"the stripped output names `{forbidden}`, which the target profile does not compile; \
this is the cross-profile leak the DoD's 串味 assertion forbids.\nsource:\n{source}"
);
}
assert!(
source.contains("base_mut()"),
"the stripped output must construct the tree imperatively (d-3)"
);
assert!(
source.contains("try_add_child"),
"the stripped output must add children through the capacity-reporting API (d-4)"
);
}
#[test]
fn the_default_output_uses_the_declarative_seam() {
let generated = generate(&request(TargetProfile::Default)).expect("generation must succeed");
assert!(
generated.source.contains("rust_widgets::view::Node"),
"the default template builds a `Node` tree (D7: it reuses the diff engine)"
);
assert!(
generated.source.contains("ViewEngine"),
"the default template mounts through `ViewEngine`, which is the two modes' shared seam"
);
assert!(
!generated.source.contains("try_add_child"),
"the default template must not also build imperatively; that would be two trees"
);
}
#[test]
fn a_container_over_capacity_is_reported_for_the_stripped_target_only() {
use rust_widgets::designer::MINI_CHILD_CAPACITY;
let mut children = String::new();
for index in 0..(MINI_CHILD_CAPACITY + 1) {
if index > 0 {
children.push(',');
}
children.push_str(&format!("{{\"label\":{{\"text\":\"c{index}\"}}}}"));
}
let json = format!("{{\"window\":{{\"id\":\"w\",\"children\":[{children}]}}}}");
let stripped = generate(&GenerationRequest {
json: json.clone(),
target: TargetProfile::Stripped,
width: 400,
height: 300,
function_name: String::from("build_ui"),
})
.expect("generation must succeed");
assert!(
!stripped.report.capacity_overflow.is_empty(),
"{} children exceeds the stripped target's capacity of {MINI_CHILD_CAPACITY} and must be \
reported, not emitted: {}",
MINI_CHILD_CAPACITY + 1,
stripped.report.summary()
);
let default = generate(&GenerationRequest {
json,
target: TargetProfile::Default,
width: 400,
height: 300,
function_name: String::from("build_ui"),
})
.expect("generation must succeed");
assert!(
default.report.capacity_overflow.is_empty(),
"a heap-allocating target has room for these children; reporting them would be noise"
);
}