// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial //! Test that all styles have the same API. use i_slint_compiler::langtype::Type; use i_slint_compiler::object_tree::PropertyVisibility; use i_slint_compiler::typeloader::TypeLoader; use i_slint_compiler::typeregister::TypeRegister; use std::collections::BTreeMap; use std::collections::HashSet; use std::fmt::Display; use std::rc::Rc; #[derive(PartialEq, Debug)] struct PropertyInfo { ty: Type, vis: PropertyVisibility, pure: bool, } impl Display for PropertyInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}/{}{:?}", self.ty, if self.pure { "pure-" } else { "" }, self.vis) } } #[derive(Default)] struct Component { properties: BTreeMap, } #[derive(Default)] struct Style { components: BTreeMap, structs: BTreeMap, } fn load_component(component: &Rc) -> Component { let mut result = Component::default(); let mut elem = component.root_element.clone(); loop { result.properties.extend( elem.borrow() .property_declarations .iter() .filter(|(_, v)| v.visibility != PropertyVisibility::Private) .map(|(k, v)| { ( k.clone(), PropertyInfo { ty: v.property_type.clone(), vis: v.visibility, pure: v.pure.unwrap_or(false), }, ) }), ); let e = match &elem.borrow().base_type { i_slint_compiler::langtype::ElementType::Component(r) => r.root_element.clone(), i_slint_compiler::langtype::ElementType::Builtin(b) => { let builtins = i_slint_compiler::typeregister::reserved_properties() .map(|x| x.0.to_owned()) .collect::>(); result.properties.extend( b.properties.iter().filter(|(k, _)| !builtins.contains(*k)).map(|(k, v)| { ( k.clone(), PropertyInfo { ty: v.ty.clone(), vis: v.property_visibility, pure: false, }, ) }), ); break; } i_slint_compiler::langtype::ElementType::Native(_) => unreachable!(), i_slint_compiler::langtype::ElementType::Error => unreachable!(), i_slint_compiler::langtype::ElementType::Global => break, }; elem = e; } result } fn load_style(style_name: String) -> Style { let mut config = i_slint_compiler::CompilerConfiguration::new( i_slint_compiler::generator::OutputFormat::Llr, ); config.style = Some(style_name); let mut diag = i_slint_compiler::diagnostics::BuildDiagnostics::default(); let mut loader = TypeLoader::new(TypeRegister::builtin(), config, &mut diag); // ensure that the style is loaded spin_on::spin_on(loader.import_component("std-widgets.slint", "Button", &mut diag)); if diag.has_error() { #[cfg(feature = "display-diagnostics")] diag.print(); panic!("error parsing style {}", loader.compiler_config.style.as_ref().unwrap()); } let doc = loader.get_document(&loader.resolve_import_path(None, "std-widgets.slint").0).unwrap(); let mut style = Style::default(); for (name, what) in doc.exports.iter() { let name = &**name; match what { itertools::Either::Left(component) => { let component = load_component(component); let old = style.components.insert(name.clone(), component); assert!( old.is_none(), "Duplicated component '{name}' in style {}", loader.compiler_config.style.as_ref().unwrap() ); } itertools::Either::Right(ty) => { let old = style.structs.insert(name.clone(), ty.clone()); assert!( old.is_none(), "Duplicated struct '{name}' in style {}", loader.compiler_config.style.as_ref().unwrap() ); } } } style } fn compare_styles(base: &Style, mut other: Style, style_name: &str) -> bool { let mut ok = true; for (compo_name, c1) in base.components.iter() { // These more or less internals component can have different properties let ignore_extra = matches!(compo_name.as_str(), "TabImpl" | "StyleMetrics"); if let Some(mut c2) = other.components.remove(compo_name) { for (prop_name, p1) in c1.properties.iter() { if let Some(p2) = c2.properties.remove(prop_name) { if p1 != &p2 { eprintln!("Mismatch property info '{compo_name}::{prop_name}' in {style_name} : {p1} != {p2}",); ok = false; } } else if !ignore_extra { eprintln!("Property '{compo_name}::{prop_name}' not found in {style_name}"); ok = false; } } // Extra property on StyleMetrics are allowed if !c2.properties.is_empty() && !ignore_extra { for prop_name in c2.properties.keys() { eprintln!("Extra property '{compo_name}::{prop_name}' found in {style_name}"); } ok = false; } } else { eprintln!("Component '{compo_name}' not found in {style_name}"); ok = false; } } if !other.components.is_empty() { for compo_name in other.components.keys() { eprintln!("Extra component '{compo_name}' found in {style_name}"); } ok = false; } if base.structs != other.structs { eprintln!( "Mismatch struct export in '{style_name}': {:?} != {:?}", base.structs, other.structs ); ok = false; } ok } #[test] fn check_styles() { let base = load_style("fluent".into()); let mut ok = true; for s in i_slint_compiler::fileaccess::styles() { let other = load_style(s.into()); ok &= compare_styles(&base, other, s); } assert!(ok); }