blob: bc1429ce3e579a05b3f6154d8702e57c8936e890 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! Pure presentation protocols shared by schema generation and native renderers.
pub mod terminal;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn responsive_stack_validates_fraction_bounds_and_direct_child_overrides() {
let base = json!({"kind":"stack","key":"root","direction":"column","gap":0,"padding":0,"background":0,
"wide":{"min_width":100,"direction":"row","children":{"list":{"basis_fraction":{"numerator":34,"denominator":100}}}},
"children":[{"kind":"text","key":"list","text":"x","color":0}]});
let validate = |value| {
serde_json::from_value::<terminal::Node>(value)
.unwrap()
.validate_root()
};
validate(base.clone()).unwrap();
for invalid in [
json!({"numerator":1,"denominator":0}),
json!({"numerator":2,"denominator":1}),
json!({"numerator":1,"denominator":3,"min":6,"max":5}),
] {
let mut value = base.clone();
value["wide"]["children"]["list"]["basis_fraction"] = invalid.clone();
assert!(validate(value).is_err());
let mut value = base.clone();
value["layout"] = json!({"basis_fraction":invalid});
assert!(validate(value).is_err());
}
for (pointer, invalid) in [
("/wide/min_width", json!(0)),
("/wide/children", json!({"missing":{"basis":10}})),
(
"/wide/children/list",
json!({"basis":1,"basis_fraction":{"numerator":1,"denominator":2}}),
),
("/wide/children/list", json!({"wrap":true})),
] {
let mut value = base.clone();
*value.pointer_mut(pointer).unwrap() = invalid;
assert!(validate(value).is_err());
}
let mut value = base;
value["scroll"] = json!("manual");
assert!(
validate(value).is_err(),
"a scrolling column cannot switch to a row"
);
}
#[test]
fn wheel_commands_require_a_viewport_and_share_the_original_input_contract() {
let value = json!({"kind":"stack","key":"list","direction":"column","gap":0,"padding":0,"background":0,"children":[],"scroll":"manual","wheel":{"up":"previous","down":"next"}});
let node: terminal::Node = serde_json::from_value(value.clone()).unwrap();
node.validate_root().unwrap();
let mut inputs = std::collections::BTreeMap::new();
inputs = node.metadata(inputs).unwrap().inputs;
assert_eq!(inputs.len(), 2);
assert_eq!(inputs["next"], json!(true));
inputs.insert("next".into(), json!({"type":"string"}));
assert!(node.metadata(inputs).is_err());
for (field, replacement) in [
("scroll", json!("none")),
("wheel", json!({"up":"","down":"next"})),
] {
let mut invalid = value.clone();
invalid[field] = replacement;
let node: terminal::Node = serde_json::from_value(invalid).unwrap();
assert!(node.validate_root().is_err());
}
let mut complete = value.clone();
complete["wheel"]["down"] = json!("x".repeat(129));
complete["padding"] = json!(u16::MAX);
complete["gap"] = json!(u16::MAX);
let node: terminal::Node = serde_json::from_value(complete.clone()).unwrap();
node.validate_root().unwrap();
assert_eq!(
node.metadata(Default::default()).unwrap().inputs[&"x".repeat(129)],
json!(true)
);
complete["padding"] = json!(u32::from(u16::MAX) + 1);
assert!(
serde_json::from_value::<terminal::Node>(complete).is_err(),
"native geometry remains representable as u16"
);
}
#[test]
fn parent_commands_check_native_inputs_in_later_descendants() {
let text = |key| json!({"kind":"text","key":key,"text":"Body","color":0});
let mut source = json!({"kind":"stack","key":"root","direction":"column","gap":0,"padding":0,"background":0,
"shortcuts":[{"key":"k","modifiers":["primary"],"input":"target","enabled":true}],
"children":[{"kind":"item","key":"target","content":text("body")}]});
let terminal = |source| {
let node: terminal::Node = serde_json::from_value(source).unwrap();
node.validate_root().unwrap();
node.metadata(Default::default())
};
assert_eq!(
terminal(source.clone()).err().unwrap(),
"shortcut collides with typed native input"
);
source["shortcuts"] = json!([]);
source["scroll"] = json!("manual");
source["wheel"] = json!({"up":"target","down":"next"});
assert_eq!(
terminal(source.clone()).err().unwrap(),
"wheel command collides with typed native input"
);
source["shortcuts"] =
json!([{"key":"k","modifiers":["primary"],"input":"target","enabled":true}]);
source.as_object_mut().unwrap().remove("wheel");
source["children"] = json!([{"kind":"button","key":"target","label":"Action"}]);
assert_eq!(
terminal(source.clone()).unwrap().inputs["target"],
json!(true)
);
}
#[test]
fn copy_button_keeps_complete_text_and_no_conflicting_native_default() {
{
let text = String::new();
let value = json!({"kind":"button","key":"copy","label":"Copy","copy_text":text});
assert!(
serde_json::from_value::<terminal::Node>(value.clone())
.unwrap()
.validate_root()
.is_err()
);
}
let value =
json!({"kind":"button","key":"copy","label":"Copy","copy_text":"界".repeat(24 * 1024)});
serde_json::from_value::<terminal::Node>(value.clone())
.unwrap()
.validate_root()
.unwrap();
let mut conflict = value;
conflict["focus_child"] = json!("editor");
assert!(
serde_json::from_value::<terminal::Node>(conflict)
.unwrap()
.validate_root()
.is_err()
);
}
}