//! What operations say they are about (ARCH §4.3a). //! //! The point of an attribute is that a panel can group operations without //! knowing what any of them is. These assert the properties that makes //! possible, over the real chain rather than over fixtures — including the //! hand-written operations, which `build.rs` never sees and so cannot check. use dr_pipeline::{Attribute, EditGraph}; /// The invariant the whole scheme rests on. /// /// An operation with no attribute is invisible to a panel that groups by them. /// `build.rs` refuses to *generate* one; this covers the ones it does not /// generate. #[test] fn every_operation_says_what_it_is_about() { let graph = EditGraph::default_chain(); for cap in graph.capabilities() { assert!( !cap.attributes.is_empty(), "{} declares no attribute, so no panel grouping by attribute \ would ever show it", cap.id.0 ); } } /// A panel can build its groups from the chain alone, with no table mapping /// operations to groups — which is what lets `ui/` name no operation /// (FR-DEV-3a). #[test] fn the_groups_are_derivable_from_the_chain() { let graph = EditGraph::default_chain(); let caps = graph.capabilities(); let mut present: Vec = caps .iter() .flat_map(|c| c.attributes.iter().copied()) .collect(); present.sort(); present.dedup(); assert!( present.contains(&Attribute::Tone), "the chain has tonal work" ); assert!(present.contains(&Attribute::Colour)); assert!( present.contains(&Attribute::Geometry), "framing is in the capability list and is geometry" ); // And nothing derived is empty: a group with no operations in it would be // a tab that opens onto nothing. for a in &present { assert!( caps.iter().any(|c| c.attributes.contains(a)), "{a:?} appeared with nothing in it" ); } } /// The case the plural exists for. #[test] fn an_operation_may_be_about_two_things() { let graph = EditGraph::default_chain(); let curve = graph .capabilities() .into_iter() .find(|c| c.id.0 == "tone_curve") .expect("the chain has a tone curve"); assert!(curve.attributes.contains(&Attribute::Tone)); assert!( curve.attributes.contains(&Attribute::Colour), "the per-channel curves are chromatic; filing it under tone alone \ would hide it from half the people looking for it" ); } /// Attributes describe the operation, never the screen — so a frontend that /// groups by them can still reach every parameter, and one that ignores them /// loses nothing. #[test] fn grouping_reaches_every_parameter() { let graph = EditGraph::default_chain(); let caps = graph.capabilities(); let ungrouped: usize = caps.iter().map(|c| c.params.len()).sum(); let reachable: usize = caps .iter() .filter(|c| Attribute::ALL.iter().any(|a| c.attributes.contains(a))) .map(|c| c.params.len()) .sum(); assert_eq!( reachable, ungrouped, "a panel showing every attribute must show every parameter" ); } /// The vocabulary is closed, so a typo cannot invent a category holding one /// operation — which is indistinguishable from a deliberate new one until /// somebody notices the tab with a single control in it. #[test] fn the_vocabulary_is_closed() { assert_eq!(Attribute::from_name("tone"), Some(Attribute::Tone)); assert_eq!(Attribute::from_name("colour"), Some(Attribute::Colour)); assert_eq!(Attribute::from_name("Tone"), None, "names are lower case"); assert_eq!(Attribute::from_name("color"), None, "and are spelled once"); assert_eq!(Attribute::from_name("tonal"), None); } /// Every attribute names a concept the localiser can resolve, the way an /// operation's own label does. #[test] fn every_attribute_is_nameable() { for a in Attribute::ALL { let key = a.label().0; assert!(key.starts_with("attr."), "{a:?} has key {key:?}"); assert!(key.len() > "attr.".len()); } }