diff --git a/crates/format/src/fig/layer_cap_tests.rs b/crates/format/src/fig/layer_cap_tests.rs index 6eeb15f4..2c562ba7 100644 --- a/crates/format/src/fig/layer_cap_tests.rs +++ b/crates/format/src/fig/layer_cap_tests.rs @@ -103,6 +103,27 @@ fn instances_of_a_main_the_cap_kept_out_say_so() { assert!(!doc.nodes().any(|n| n.name == "Local main")); } +#[test] +fn swap_defaults_the_cap_kept_out_say_so() { + // A kept main's instance swap default names a main with no instances, which comes after + // enough layers to be skipped at the cap. + let swap = o(vec![ + ("id", g(70)), + ("name", Kv::Str("Icon".into())), + ("type", Kv::Enum("INSTANCE_SWAP")), + ("initialValue", o(vec![("guidValue", g(900))])), + ]); + let mut nodes = vec![node(1, None, "DOCUMENT", "Document", vec![]), node(2, Some(1), "CANVAS", "Page", vec![])]; + nodes.push(node(50, Some(2), "SYMBOL", "Card", vec![("size", xy(10.0, 10.0)), ("componentPropDefs", Kv::Array(vec![swap]))])); + nodes.extend(main(60, 2, "Filler", 40)); + nodes.extend(main(900, 2, "Icon main", 10)); + let (doc, warnings) = import_at(&o(vec![("nodeChanges", Kv::Array(nodes))]), 30); + assert!(doc.nodes().any(|n| n.name == "Card")); + assert!(!doc.nodes().any(|n| n.name == "Icon main")); + assert!(warnings.iter().any(|w| w.starts_with("instance swap properties whose default component was past the layer limit")), "{warnings:?}"); + assert!(!warnings.iter().any(|w| w.contains("isn't in the file")), "{warnings:?}"); +} + #[test] fn a_whole_file_past_the_cap_keeps_its_library_links_and_says_so_once() { // A 1 001-layer library main used 210 times: about 210 000 layers in a file far too small to diff --git a/crates/format/src/fig/map.rs b/crates/format/src/fig/map.rs index 7eb974da..b18f8a2b 100644 --- a/crates/format/src/fig/map.rs +++ b/crates/format/src/fig/map.rs @@ -45,7 +45,7 @@ pub(super) fn to_rest(msg: &Kv, name: &str, max_nodes: usize) -> (Value, Vec= m.max_nodes { - let capped: Vec = m.instance_mains.difference(&m.mapped_mains).map(|id| json!(id)).collect(); + let capped: Vec = m.referenced_mains.difference(&m.mapped_mains).map(|id| json!(id)).collect(); if !capped.is_empty() { out[crate::rest::FIG_CAPPED_MAINS_FIELD] = Value::Array(capped); } @@ -397,9 +397,9 @@ struct Mapper<'a> { library_scan_count: usize, warnings: Vec, count: usize, - /// The mains in the file that instances name, and the mains mapped as layers: the difference - /// is what the layer cap kept out. - instance_mains: BTreeSet, + /// The mains in the file that instances or instance swap defaults name, and the mains mapped + /// as layers: the difference is what the layer cap kept out. + referenced_mains: BTreeSet, mapped_mains: BTreeSet, /// Most layers produced or scanned, instance expansion included; the one place the cap comes /// from (see [`super::layer_budget`]). @@ -478,7 +478,7 @@ impl<'a> Mapper<'a> { library_scan_count: 0, warnings: Vec::new(), count: 0, - instance_mains: BTreeSet::new(), + referenced_mains: BTreeSet::new(), mapped_mains: BTreeSet::new(), max_nodes, networks: Vec::new(), @@ -1869,7 +1869,12 @@ impl<'a> Mapper<'a> { "BOOL" => json!({"type": "BOOLEAN", "defaultValue": init.and_then(|i| i.get("boolValue")).and_then(Kv::bool).unwrap_or(true)}), "TEXT" => json!({"type": "TEXT", "defaultValue": init.and_then(prop_text).unwrap_or("")}), "INSTANCE_SWAP" => match init.and_then(|i| i.get("guidValue")).and_then(guid) { - Some(g) => json!({"type": "INSTANCE_SWAP", "defaultValue": g}), + Some(g) => { + if self.nodes.contains_key(&g) { + self.referenced_mains.insert(g.clone()); + } + json!({"type": "INSTANCE_SWAP", "defaultValue": g}) + } None => continue, }, "VARIANT" => json!({"type": "VARIANT"}), @@ -2013,8 +2018,8 @@ impl<'a> Mapper<'a> { self.warn("instances of components that aren't in the file show no layers"); return None; } - if !self.instance_mains.contains(&sym) { - self.instance_mains.insert(sym.clone()); + if !self.referenced_mains.contains(&sym) { + self.referenced_mains.insert(sym.clone()); } if outer.is_some_and(|i| i.symbols.contains(&sym)) { return None;