mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-29 12:56:47 -06:00
refactor(fig): split mapping and lint test helpers
This commit is contained in:
@@ -274,19 +274,30 @@ fn variables_modes_and_bindings_round_trip() {
|
||||
#[test]
|
||||
fn variables_are_written_as_nodes_on_a_hidden_page() {
|
||||
let nodes = written(&themed());
|
||||
let hidden = named(&nodes, "Internal Only Canvas");
|
||||
assert_hidden_theme(&nodes);
|
||||
assert_variable_values(&nodes);
|
||||
assert_variable_bindings(&nodes);
|
||||
}
|
||||
|
||||
fn assert_hidden_theme(nodes: &[Value]) {
|
||||
let hidden = named(nodes, "Internal Only Canvas");
|
||||
assert_eq!((hidden["type"].as_str(), hidden["internalOnly"].as_bool(), hidden["visible"].as_bool()), (Some("CANVAS"), Some(true), Some(false)));
|
||||
let set = named(&nodes, "Theme");
|
||||
let set = named(nodes, "Theme");
|
||||
assert_eq!(set["type"], "VARIABLE_SET");
|
||||
assert_eq!(set["parentIndex"]["guid"], hidden["guid"]);
|
||||
let modes = set["variableSetModes"].as_array().unwrap();
|
||||
assert_eq!(modes.iter().map(|m| m["name"].as_str().unwrap()).collect::<Vec<_>>(), ["Light", "Dark"]);
|
||||
let bg = named(&nodes, "color/bg");
|
||||
}
|
||||
|
||||
fn assert_variable_values(nodes: &[Value]) {
|
||||
let set = named(nodes, "Theme");
|
||||
let modes = set["variableSetModes"].as_array().unwrap();
|
||||
let bg = named(nodes, "color/bg");
|
||||
assert_eq!((bg["type"].as_str(), bg["variableResolvedType"].as_str()), (Some("VARIABLE"), Some("COLOR")));
|
||||
assert_eq!(bg["variableSetID"]["guid"], set["guid"]);
|
||||
assert_eq!(bg["description"], "Behind everything");
|
||||
assert_eq!(bg["variableScopes"], json!(["FRAME_FILL", "SHAPE_FILL"]));
|
||||
assert_eq!(named(&nodes, "color/fg")["variableScopes"], json!(["TEXT_FILL", "STROKE", "EFFECT_COLOR"]));
|
||||
assert_eq!(named(nodes, "color/fg")["variableScopes"], json!(["TEXT_FILL", "STROKE", "EFFECT_COLOR"]));
|
||||
let values = bg["variableDataValues"]["entries"].as_array().unwrap();
|
||||
assert_eq!(values.len(), 2);
|
||||
assert_eq!(values[1]["modeID"], modes[1]["id"]);
|
||||
@@ -295,19 +306,25 @@ fn variables_are_written_as_nodes_on_a_hidden_page() {
|
||||
(Some("COLOR"), Some(0.25))
|
||||
);
|
||||
// An alias names the variable's node; every value says the type it resolves to.
|
||||
let accent = &named(&nodes, "color/accent")["variableDataValues"]["entries"];
|
||||
let accent = &named(nodes, "color/accent")["variableDataValues"]["entries"];
|
||||
assert_eq!(accent[0]["variableData"]["dataType"], "ALIAS");
|
||||
assert_eq!(accent[0]["variableData"]["resolvedDataType"], "COLOR");
|
||||
assert_eq!(accent[0]["variableData"]["value"]["alias"]["guid"], named(&nodes, "color/fg")["guid"]);
|
||||
assert_eq!(named(&nodes, "title")["variableDataValues"]["entries"][1]["variableData"]["value"]["textValue"], "Grüße 😀");
|
||||
assert_eq!(named(&nodes, "shown")["variableDataValues"]["entries"][1]["variableData"]["value"]["boolValue"], false);
|
||||
assert_eq!(named(&nodes, "space/gap")["variableDataValues"]["entries"][0]["variableData"]["value"]["floatValue"], 8.0);
|
||||
assert_eq!(accent[0]["variableData"]["value"]["alias"]["guid"], named(nodes, "color/fg")["guid"]);
|
||||
assert_eq!(named(nodes, "title")["variableDataValues"]["entries"][1]["variableData"]["value"]["textValue"], "Grüße 😀");
|
||||
assert_eq!(named(nodes, "shown")["variableDataValues"]["entries"][1]["variableData"]["value"]["boolValue"], false);
|
||||
assert_eq!(named(nodes, "space/gap")["variableDataValues"]["entries"][0]["variableData"]["value"]["floatValue"], 8.0);
|
||||
}
|
||||
|
||||
fn assert_variable_bindings(nodes: &[Value]) {
|
||||
let bg = named(nodes, "color/bg");
|
||||
let set = named(nodes, "Theme");
|
||||
let modes = set["variableSetModes"].as_array().unwrap();
|
||||
// Bindings: on the paint, the gradient stop, the effect and the grid, and in the layer's map.
|
||||
let card = named(&nodes, "Card");
|
||||
let card = named(nodes, "Card");
|
||||
assert_eq!(card["fillPaints"][0]["colorVar"]["value"]["alias"]["guid"], bg["guid"]);
|
||||
assert_eq!(card["variableModeBySetMap"]["entries"][0]["variableSetID"]["guid"], set["guid"]);
|
||||
assert_eq!(card["variableModeBySetMap"]["entries"][0]["variableModeID"], modes[1]["id"]);
|
||||
assert_eq!(card["layoutGrids"][0]["gutterSizeVar"]["value"]["alias"]["guid"], named(&nodes, "space/gap")["guid"]);
|
||||
assert_eq!(card["layoutGrids"][0]["gutterSizeVar"]["value"]["alias"]["guid"], named(nodes, "space/gap")["guid"]);
|
||||
let fields: Vec<&str> =
|
||||
card["variableConsumptionMap"]["entries"].as_array().unwrap().iter().map(|e| e["variableField"].as_str().unwrap()).collect();
|
||||
for f in
|
||||
@@ -315,7 +332,7 @@ fn variables_are_written_as_nodes_on_a_hidden_page() {
|
||||
{
|
||||
assert!(fields.contains(&f), "{f} in {fields:?}");
|
||||
}
|
||||
let swatch = named(&nodes, "Swatch");
|
||||
let swatch = named(nodes, "Swatch");
|
||||
assert!(swatch["fillPaints"][1]["stopsVar"][0].get("colorVar").is_none());
|
||||
assert_eq!(swatch["fillPaints"][1]["stopsVar"][1]["colorVar"]["value"]["alias"]["guid"], bg["guid"]);
|
||||
assert_eq!(swatch["fillPaints"][1]["stopsVar"].as_array().unwrap().len(), 2);
|
||||
@@ -324,7 +341,7 @@ fn variables_are_written_as_nodes_on_a_hidden_page() {
|
||||
assert_eq!(swatch["effects"][0][key]["dataType"], "ALIAS", "{key}");
|
||||
}
|
||||
// The page sets a mode too.
|
||||
assert_eq!(named(&nodes, "Page 1")["variableModeBySetMap"]["entries"][0]["variableSetID"]["guid"], named(&nodes, "Spacing")["guid"]);
|
||||
assert_eq!(named(nodes, "Page 1")["variableModeBySetMap"]["entries"][0]["variableSetID"]["guid"], named(nodes, "Spacing")["guid"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2,7 +2,10 @@ use super::component::{Entry, Inst, OverrideRow, prop_ref};
|
||||
use super::connector::connector_has_saved_label;
|
||||
use super::layout::{layout_grid, tracks};
|
||||
use super::paint::{effect, export_setting, paint, paint_bound, stroke};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, BTreeMap, BTreeSet, Kv, MAX_COMMANDS, MAX_DEPTH, Mapper, Obj, Value, View, copy_num, copy_str, guid, json, matrix, network, num, rows,
|
||||
size,
|
||||
};
|
||||
|
||||
/// How the layers of a resized container follow it when the file gives no derived layout for
|
||||
/// them: a frame's by their constraints, from its main's size to its own; a group's scale with it.
|
||||
@@ -61,6 +64,22 @@ impl Fit {
|
||||
}
|
||||
}
|
||||
|
||||
struct LayerChildren<'s, 'a> {
|
||||
id: &'s str,
|
||||
inst: Option<&'s Inst<'a>>,
|
||||
base: &'s Kv<'a>,
|
||||
child_space: Affine,
|
||||
view: &'s View<'a>,
|
||||
object: &'s mut Obj,
|
||||
ty: &'s str,
|
||||
rest_id: &'s str,
|
||||
connector_snapshot: bool,
|
||||
connector_terminal: bool,
|
||||
depth: usize,
|
||||
included: Option<&'s BTreeSet<String>>,
|
||||
slot_content: Option<String>,
|
||||
}
|
||||
|
||||
impl<'a> Mapper<'a> {
|
||||
/// Layer `id` (inside instance `inst` when expanding one) and its subtree. `space`: from the
|
||||
/// parent's coordinates to the enclosing frame's (groups and booleans place their children in
|
||||
@@ -136,16 +155,7 @@ impl<'a> Mapper<'a> {
|
||||
connector_snapshot && (!connector_has_saved_label(&view) || depth >= MAX_DEPTH || self.count.saturating_add(1) >= self.budget());
|
||||
// A supported connector snapshot has its own note below instead of the generic stand-in one.
|
||||
let stand = stand_in(own).filter(|_| !connector_snapshot);
|
||||
let ty = match own {
|
||||
"ROUNDED_RECTANGLE" => "RECTANGLE",
|
||||
"SYMBOL" => "COMPONENT",
|
||||
"FRAME" if view.flag("isStateGroup") == Some(true) => "COMPONENT_SET",
|
||||
"FRAME" if view.flag("resizeToFit") == Some(true) => "GROUP",
|
||||
"VARIABLE" | "VARIABLE_SET" => return None,
|
||||
"CONNECTOR" if connector_snapshot && connector_terminal => "VECTOR",
|
||||
"CONNECTOR" if connector_snapshot => "FRAME",
|
||||
t => stand.map_or(t, |s| s.0),
|
||||
};
|
||||
let ty = Self::mapped_type(own, &view, connector_snapshot, connector_terminal, stand)?;
|
||||
if let Some((_, what)) = stand.filter(|_| own != "TEXT_PATH") {
|
||||
self.warn(format!("{} layers import as {what}", own.to_ascii_lowercase().replace('_', " ")));
|
||||
}
|
||||
@@ -225,18 +235,59 @@ impl<'a> Mapper<'a> {
|
||||
source.insert("slotOverride".into(), json!(true));
|
||||
}
|
||||
let child_space = if matches!(ty, "GROUP" | "BOOLEAN_OPERATION") { space * view.transform().unwrap_or_default() } else { Affine::IDENTITY };
|
||||
let mut kids = if connector_snapshot && !connector_terminal { self.connector_snapshot_children(&view, &rest_id) } else { Vec::new() };
|
||||
let mut kids = self.map_layer_children(LayerChildren {
|
||||
id,
|
||||
inst,
|
||||
base,
|
||||
child_space,
|
||||
view: &view,
|
||||
object: &mut o,
|
||||
ty,
|
||||
rest_id: &rest_id,
|
||||
connector_snapshot,
|
||||
connector_terminal,
|
||||
depth,
|
||||
included,
|
||||
slot_content,
|
||||
});
|
||||
if stand.is_some_and(|s| s.0 == "FRAME") {
|
||||
kids.splice(0..0, self.shape_with_text(&mut o, &view, own, &rest_id));
|
||||
kids.extend(self.inner_text(&o, &view, &rest_id, own == "STICKY"));
|
||||
}
|
||||
if !kids.is_empty() {
|
||||
o.insert("children".into(), Value::Array(kids));
|
||||
}
|
||||
Some(Value::Object(o))
|
||||
}
|
||||
|
||||
fn map_layer_children(&mut self, context: LayerChildren<'_, 'a>) -> Vec<Value> {
|
||||
let LayerChildren {
|
||||
id,
|
||||
inst,
|
||||
base,
|
||||
child_space,
|
||||
view,
|
||||
object: o,
|
||||
ty,
|
||||
rest_id,
|
||||
connector_snapshot,
|
||||
connector_terminal,
|
||||
depth,
|
||||
included,
|
||||
slot_content,
|
||||
} = context;
|
||||
let mut kids = if connector_snapshot && !connector_terminal { self.connector_snapshot_children(view, rest_id) } else { Vec::new() };
|
||||
if connector_terminal {
|
||||
// The saved vector is flattened into the connector node when no child slot remains.
|
||||
} else if ty == "INSTANCE" {
|
||||
if let Some((sub, sym)) = self.instance(&mut o, &view, id, inst, &rest_id) {
|
||||
if let Some((sub, sym)) = self.instance(o, view, id, inst, rest_id) {
|
||||
let main = self.nodes.get(&sym).map(|m| View::of(m));
|
||||
let fit = Fit::frame(main.as_ref().and_then(View::size), view.size());
|
||||
// An instance that records no auto layout of its own has its main's.
|
||||
let view = match main {
|
||||
Some(m) if view.get("stackMode").is_none() && m.get("stackMode").is_some() => {
|
||||
let layered = View { fitted: view.fitted, ..m.with(view.layers.iter().copied()) };
|
||||
self.auto_layout(&mut o, &layered);
|
||||
self.auto_layout(o, &layered);
|
||||
layered
|
||||
}
|
||||
_ => view.clone(),
|
||||
@@ -252,7 +303,7 @@ impl<'a> Mapper<'a> {
|
||||
content_inst.slot_content = true;
|
||||
}
|
||||
for c in self.children(&content) {
|
||||
kids.extend(self.layer_filtered(&c, content_inst.as_ref(), child_space, Some(&view), None, depth + 1, included));
|
||||
kids.extend(self.layer_filtered(&c, content_inst.as_ref(), child_space, Some(view), None, depth + 1, included));
|
||||
}
|
||||
} else {
|
||||
// Inside an instance a layer's children are its main's: they follow it from the main's
|
||||
@@ -263,17 +314,29 @@ impl<'a> Mapper<'a> {
|
||||
(Some(_), _) => Fit::frame(View::of(base).size(), view.size()),
|
||||
};
|
||||
for c in self.children(id) {
|
||||
kids.extend(self.layer_filtered(&c, inst, child_space, Some(&view), fit, depth + 1, included));
|
||||
kids.extend(self.layer_filtered(&c, inst, child_space, Some(view), fit, depth + 1, included));
|
||||
}
|
||||
}
|
||||
if stand.is_some_and(|s| s.0 == "FRAME") {
|
||||
kids.splice(0..0, self.shape_with_text(&mut o, &view, own, &rest_id));
|
||||
kids.extend(self.inner_text(&o, &view, &rest_id, own == "STICKY"));
|
||||
kids
|
||||
}
|
||||
|
||||
fn mapped_type<'s>(
|
||||
own: &'s str,
|
||||
view: &View<'a>,
|
||||
connector_snapshot: bool,
|
||||
connector_terminal: bool,
|
||||
stand: Option<(&'static str, &'static str)>,
|
||||
) -> Option<&'s str> {
|
||||
match own {
|
||||
"ROUNDED_RECTANGLE" => Some("RECTANGLE"),
|
||||
"SYMBOL" => Some("COMPONENT"),
|
||||
"FRAME" if view.flag("isStateGroup") == Some(true) => Some("COMPONENT_SET"),
|
||||
"FRAME" if view.flag("resizeToFit") == Some(true) => Some("GROUP"),
|
||||
"VARIABLE" | "VARIABLE_SET" => None,
|
||||
"CONNECTOR" if connector_snapshot && connector_terminal => Some("VECTOR"),
|
||||
"CONNECTOR" if connector_snapshot => Some("FRAME"),
|
||||
t => Some(stand.map_or(t, |s| s.0)),
|
||||
}
|
||||
if !kids.is_empty() {
|
||||
o.insert("children".into(), Value::Array(kids));
|
||||
}
|
||||
Some(Value::Object(o))
|
||||
}
|
||||
|
||||
/// A non-rectangular shape-with-text's shape, as a vector under its frame (which gives up its
|
||||
|
||||
@@ -983,6 +983,11 @@ fn assert_external_namespace(doc: &Document) {
|
||||
let copied_frame = child(doc, copied_body, 0);
|
||||
assert_eq!(doc.node(frame).unwrap().source, None, "materialized ordinary layers are owned by the frame");
|
||||
assert_eq!(doc.node(copied_frame).unwrap().source, Some(frame));
|
||||
assert_external_slots(doc, frame, copied_frame, main);
|
||||
assert_external_copy_state(doc, frame, copied_frame);
|
||||
}
|
||||
|
||||
fn assert_external_slots(doc: &Document, frame: NodeId, copied_frame: NodeId, main: NodeId) {
|
||||
for index in 0..2 {
|
||||
let slot = child(doc, frame, index);
|
||||
let copied_slot = child(doc, copied_frame, index);
|
||||
@@ -999,6 +1004,9 @@ fn assert_external_namespace(doc: &Document) {
|
||||
assert_eq!(doc.node(child(doc, current, 0)).unwrap().source, Some(child(doc, inherited, 0)));
|
||||
assert_eq!(doc.node(child(doc, current, 0)).unwrap().transform.translation().x, 4.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_external_copy_state(doc: &Document, frame: NodeId, copied_frame: NodeId) {
|
||||
let old = doc.node(child(doc, frame, 0)).unwrap();
|
||||
let current = doc.node(child(doc, copied_frame, 0)).unwrap();
|
||||
assert!(!old.visible && old.is_mask && old.opacity < 1.0);
|
||||
|
||||
@@ -630,63 +630,82 @@ fn imports_the_fixture() {
|
||||
assert!(is_fig(&bytes));
|
||||
let (doc, warnings) = import(&bytes, "fallback").unwrap();
|
||||
assert_eq!(doc.name, "Fixture");
|
||||
assert_fixture_layers(&doc);
|
||||
assert_fixture_styles(&doc);
|
||||
assert_fixture_variables(&doc);
|
||||
assert_fixture_components(&doc);
|
||||
assert_fixture_prototype(&doc, &warnings);
|
||||
}
|
||||
|
||||
fn assert_fixture_layers(doc: &Document) {
|
||||
// The internal page is skipped.
|
||||
assert_eq!(doc.pages.len(), 1);
|
||||
let page = doc.node(doc.pages[0]).unwrap();
|
||||
assert_eq!(page.name, "Page A");
|
||||
// Siblings by position: the frame comes first even though it's listed after the component.
|
||||
assert_eq!(doc.node(page.children[0]).unwrap().name, "Screen");
|
||||
let screen = named(&doc, "Screen");
|
||||
let screen = named(doc, "Screen");
|
||||
let names: Vec<&str> = screen.children.iter().map(|c| doc.node(*c).unwrap().name.as_str()).collect();
|
||||
assert_eq!(names, ["Photo", "Title", "Group", "Styled", "Card", "Button copy", "Note"]);
|
||||
// Image fill from the archive.
|
||||
let photo = named(&doc, "Photo");
|
||||
let photo = named(doc, "Photo");
|
||||
let figcraft_doc::color::PaintKind::Image(img) = &photo.fills[0].kind else { panic!("not an image fill") };
|
||||
assert_eq!(doc.images.get(&img.image).map(|i| (i.width, i.height)), Some((2, 1)));
|
||||
assert_eq!((photo.corners.radii[0], photo.stroke.weight), (6.0, 2.0));
|
||||
// Text with a bold run over "the".
|
||||
let NodeKind::Text(t) = &named(&doc, "Title").kind else { panic!("not text") };
|
||||
let NodeKind::Text(t) = &named(doc, "Title").kind else { panic!("not text") };
|
||||
assert_eq!(t.characters, "Hi there");
|
||||
assert_eq!(t.style.font_size, 16.0);
|
||||
assert_eq!(t.leading_trim, figcraft_doc::LeadingTrim::CapHeight, "vertical trim (#466)");
|
||||
assert_eq!(t.runs.len(), 1);
|
||||
assert_eq!((t.runs[0].start, t.runs[0].end, t.runs[0].style.font_style.as_str()), (3, 6, "Bold"));
|
||||
// The group's children keep their place on the page (group at 100, 100).
|
||||
assert!(matches!(named(&doc, "Group").kind, NodeKind::Group));
|
||||
let tri = named(&doc, "Tri");
|
||||
assert!(matches!(named(doc, "Group").kind, NodeKind::Group));
|
||||
let tri = named(doc, "Tri");
|
||||
assert!(matches!(&tri.kind, NodeKind::Vector { network } if !network.regions.is_empty()));
|
||||
let abs = doc.world_transform(tri.id).translation();
|
||||
assert!((abs.x - 105.0).abs() < 1e-6 && (abs.y - 106.0).abs() < 1e-6, "{abs:?}");
|
||||
assert!(matches!(named(&doc, "Star").kind, NodeKind::Star { count: 6, .. }));
|
||||
// The linked style supplies the paint.
|
||||
let styled = named(&doc, "Styled");
|
||||
assert!(matches!(named(doc, "Star").kind, NodeKind::Star { count: 6, .. }));
|
||||
}
|
||||
|
||||
fn assert_fixture_styles(doc: &Document) {
|
||||
let styled = named(doc, "Styled");
|
||||
assert_eq!(styled.styles.fill.as_deref(), Some("1:3"));
|
||||
assert!(doc.styles.iter().any(|s| s.name == "Brand"));
|
||||
let figcraft_doc::color::PaintKind::Solid { color } = styled.fills[0].kind else { panic!("not solid") };
|
||||
assert_eq!((color.r, color.g, color.b), (0.0, 0.0, 1.0));
|
||||
// Variables, the fill binding and the explicit mode.
|
||||
}
|
||||
|
||||
fn assert_fixture_variables(doc: &Document) {
|
||||
let screen = named(doc, "Screen");
|
||||
assert_eq!(doc.variables.collections.len(), 1);
|
||||
assert_eq!(doc.variables.variables[0].kind, VariableType::Color);
|
||||
assert_eq!(named(&doc, "Card").bound.get("fill").map(String::as_str), Some("1:5"));
|
||||
assert_eq!(named(doc, "Card").bound.get("fill").map(String::as_str), Some("1:5"));
|
||||
assert_eq!(screen.var_modes.get("1:4").map(String::as_str), Some("1:91"));
|
||||
// The component keeps its property; the instance is expanded with the value applied.
|
||||
let button = named(&doc, "Button");
|
||||
}
|
||||
|
||||
fn assert_fixture_components(doc: &Document) {
|
||||
let button = named(doc, "Button");
|
||||
assert!(matches!(&button.kind, NodeKind::Component { props, .. } if props.contains_key("Show icon")));
|
||||
let inst = named(&doc, "Button copy");
|
||||
let inst = named(doc, "Button copy");
|
||||
assert!(
|
||||
matches!(&inst.kind, NodeKind::Instance { component, props } if *component == button.id && props.get("Show icon") == Some(&Value::Bool(false)))
|
||||
);
|
||||
let icon = doc.node(inst.children[0]).unwrap();
|
||||
assert_eq!(icon.name, "Renamed icon");
|
||||
assert!(!icon.visible);
|
||||
// Prototype: the click navigates to the component; the flow starts at the screen.
|
||||
let it = &photo.interactions[0];
|
||||
}
|
||||
|
||||
fn assert_fixture_prototype(doc: &Document, warnings: &[String]) {
|
||||
let button = named(doc, "Button");
|
||||
let screen = named(doc, "Screen");
|
||||
let it = &named(doc, "Photo").interactions[0];
|
||||
assert!(matches!(it.actions[0], figcraft_doc::proto::Action::Navigate { dest, .. } if dest == button.id));
|
||||
assert_eq!(it.duration_ms, 250);
|
||||
let Some(NodeKind::Page { flows, .. }) = doc.node(doc.pages[0]).map(|p| &p.kind) else { panic!("no page") };
|
||||
assert_eq!((flows[0].name.as_str(), flows[0].start), ("Main flow", screen.id));
|
||||
// The sticky note comes in as a frame, with a warning.
|
||||
assert!(matches!(named(&doc, "Note").kind, NodeKind::Frame));
|
||||
assert!(matches!(named(doc, "Note").kind, NodeKind::Frame));
|
||||
assert!(warnings.iter().any(|w| w.contains("sticky layers import as frames")), "{warnings:?}");
|
||||
}
|
||||
|
||||
|
||||
@@ -209,8 +209,14 @@ fn embedded_variables_match_separate_ones() {
|
||||
#[test]
|
||||
fn interactions_triggers_actions_and_transitions() {
|
||||
let (doc, warnings) = import_with(&file_json(), Some(&variables_json()), &no_images).unwrap();
|
||||
let id = |n: &str| named(&doc, n).id;
|
||||
let button = named(&doc, "Button");
|
||||
assert_interactions(&doc, &warnings);
|
||||
assert_legacy_transition(&doc);
|
||||
assert_interactions_without_variables();
|
||||
}
|
||||
|
||||
fn assert_interactions(doc: &Document, warnings: &[String]) {
|
||||
let id = |n: &str| named(doc, n).id;
|
||||
let button = named(doc, "Button");
|
||||
let its = &button.interactions;
|
||||
assert_eq!(its.len(), 8, "{its:#?}");
|
||||
|
||||
@@ -239,18 +245,22 @@ fn interactions_triggers_actions_and_transitions() {
|
||||
assert_eq!(its[7].trigger, Trigger::MouseUp);
|
||||
assert_eq!(its[7].actions, [Action::ScrollTo { dest: id("Home"), offset: [0.0, 0.0] }], "unresolved destinations are dropped");
|
||||
|
||||
// The legacy single transition.
|
||||
let details = named(&doc, "Details");
|
||||
assert!(!has(warnings, "modifier keys"), "{warnings:?}");
|
||||
for w in ["video triggers", "without a destination"] {
|
||||
assert!(has(warnings, w), "{w}: {warnings:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_legacy_transition(doc: &Document) {
|
||||
let id = |n: &str| named(doc, n).id;
|
||||
let details = named(doc, "Details");
|
||||
assert_eq!(details.interactions.len(), 1);
|
||||
let legacy = &details.interactions[0];
|
||||
assert_eq!((&legacy.trigger, &legacy.actions[..]), (&Trigger::OnClick, &[Action::Navigate { dest: id("Home"), preserve_scroll: false }][..]));
|
||||
assert_eq!((legacy.transition, legacy.duration_ms, legacy.easing), (Transition::Dissolve, 200, Easing::EaseIn));
|
||||
}
|
||||
|
||||
assert!(!has(&warnings, "modifier keys"), "{warnings:?}");
|
||||
for w in ["video triggers", "without a destination"] {
|
||||
assert!(has(&warnings, w), "{w}: {warnings:?}");
|
||||
}
|
||||
// Without variables, set-variable actions have nothing to set.
|
||||
fn assert_interactions_without_variables() {
|
||||
let (doc, warnings) = import(&file_json(), &no_images).unwrap();
|
||||
assert_eq!(named(&doc, "Button").interactions.len(), 6);
|
||||
assert!(has(&warnings, "set-variable actions") && has(&warnings, "set-variable-mode actions"), "{warnings:?}");
|
||||
|
||||
Reference in New Issue
Block a user