use anyhow::Result; use galaxyui_core::{AppContext, SingletonEntity}; use crate::manager::SettingsManager; use crate::{Setting, SupportedPlatforms, SyncToCloud, *}; define_settings_group!(TestSettings, settings: [ simple_setting: SimpleSetting { type: bool, default: false, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Never, private: false, toml_path: "test.simple_setting", }, key_override_setting: KeyOverrideSetting { type: bool, default: true, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Never, private: true, storage_key: "KeyIsOverridden", }, hierarchy_flag: HierarchyFlag { type: bool, default: false, supported_platforms: SupportedPlatforms::ALL, sync_to_cloud: SyncToCloud::Never, private: false, toml_path: "test_section.hierarchy_flag", }, ]); pub fn init_and_register_preferences(ctx: &mut AppContext) { ctx.add_singleton_model(move |_| { crate::PublicPreferences::new(Box::< galaxyui_extras::user_preferences::in_memory::InMemoryPreferences, >::default()) }); ctx.add_singleton_model(move |_| { crate::PrivatePreferences::new(Box::< galaxyui_extras::user_preferences::in_memory::InMemoryPreferences, >::default()) }); } /// This is a helper structure that listens to events generated when /// SimpleSetting is changed. struct EventListener { got_event: bool, } impl EventListener { fn new(ctx: &mut galaxyui_core::ModelContext) -> Self { let test_settings = TestSettings::handle(ctx); ctx.subscribe_to_model(&test_settings, |me, _, event, _ctx| { // Update our internal state if we get a change event for // SimpleSetting. if matches!(event, TestSettingsChangedEvent::SimpleSetting { .. }) { me.got_event = true; } }); Self { got_event: false } } } impl galaxyui_core::Entity for EventListener { type Event = (); } #[test] fn test_setting_name_is_struct_name() { assert_eq!(SimpleSetting::setting_name(), "SimpleSetting"); } #[test] fn test_default_storage_key_is_setting_name() { assert_eq!(SimpleSetting::storage_key(), "SimpleSetting"); } #[test] fn test_can_override_storage_key() { assert_ne!( KeyOverrideSetting::setting_name(), KeyOverrideSetting::storage_key() ); assert_eq!(KeyOverrideSetting::storage_key(), "KeyIsOverridden"); } #[test] fn test_set_value_raises_changed_event_no_save() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); // Register our TestSettings settings group with the app. TestSettings::register(&mut app); // Register a model which subscribes to change events on TestSettings. let handle = app.add_model(EventListener::new); // Make sure that we haven't received any events yet. app.read(|ctx| { assert!(!handle.as_ref(ctx).got_event); }); // Modify the value of the setting. This should produce an event that // is handled by our EventListener model. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { let _ = test_settings.simple_setting.set_value(true, ctx); }); }); // Check that the EventListener model received the event. app.read(|ctx| { assert!(handle.as_ref(ctx).got_event); }); }); } #[test] fn test_set_value_raises_changed_event_save() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); // Register our TestSettings settings group with the app. TestSettings::register(&mut app); // Register a model which subscribes to change events on TestSettings. let handle = app.add_model(EventListener::new); // Make sure that we haven't received any events yet. app.read(|ctx| { assert!(!handle.as_ref(ctx).got_event); }); // Modify the value of the setting. This should produce an event that // is handled by our EventListener model. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { assert!(test_settings.simple_setting.set_value(true, ctx).is_ok()); }); }); // Check that the EventListener model received the event. app.read(|ctx| { assert!(handle.as_ref(ctx).got_event); }); }); } #[test] fn test_save_and_load_lifecycle() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); // Register our TestSettings settings group with the app. TestSettings::register(&mut app); // Confirm that the initial value of the setting is its default value. let default_value = SimpleSetting::default_value(); app.read(|ctx| { assert_eq!( *TestSettings::as_ref(ctx).simple_setting.value(), default_value ); }); // Update the value of the setting. let new_value = !default_value; TestSettings::handle(&app).update(&mut app, |test_settings, ctx| { assert!( test_settings .simple_setting .set_value(new_value, ctx) .is_ok() ); }); // Verify that the stored value is correct by creating a new instance of // the setting which is initialized from the value in storage. app.update(|ctx| { let fresh_simple_setting = SimpleSetting::new_from_storage(ctx); assert_eq!(*fresh_simple_setting.value(), new_value); assert_ne!(*fresh_simple_setting.value(), default_value); }); }); } #[test] fn test_toggleable_setting() -> Result<()> { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); // Register our TestSettings settings group with the app. TestSettings::register(&mut app); // Confirm that the initial value of the setting is its default value. let default_value = SimpleSetting::default_value(); app.read(|ctx| { assert_eq!( *TestSettings::as_ref(ctx).simple_setting.value(), default_value ); }); // Toggle the value of the setting and verify that the returned value // is not the same as the initial value. let new_value = TestSettings::handle(&app).update(&mut app, |test_settings, ctx| { test_settings.simple_setting.toggle_and_save_value(ctx) })?; assert_ne!(new_value, default_value); // Verify that the in-memory value is correct when reading it back out // of the setting. app.read(|ctx| { assert_eq!(*TestSettings::as_ref(ctx).simple_setting.value(), new_value); }); // Verify that the stored value is correct by creating a new instance of // the setting which is initialized from the value in storage. app.update(|ctx| { let fresh_simple_setting = SimpleSetting::new_from_storage(ctx); assert_eq!(*fresh_simple_setting.value(), new_value); assert_ne!(*fresh_simple_setting.value(), default_value); }); Ok(()) }) } #[test] fn test_explicit_value_tracking_with_none() { // Test that settings created with None are not marked as explicitly set let setting = SimpleSetting::new(None); assert!(!setting.is_value_explicitly_set()); assert_eq!(*setting.value(), SimpleSetting::default_value()); } #[test] fn test_explicit_value_tracking_with_some() { // Test that settings created with Some(value) are marked as explicitly set let setting = SimpleSetting::new(Some(true)); assert!(setting.is_value_explicitly_set()); assert!(*setting.value()); } #[test] fn test_explicit_value_tracking_after_set_value() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); // Register our TestSettings settings group with the app. TestSettings::register(&mut app); // Initially, the setting should not be explicitly set (default value) app.read(|ctx| { let settings = TestSettings::as_ref(ctx); assert!(!settings.simple_setting.is_value_explicitly_set()); }); // After setting a value, it should be marked as explicitly set app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { let _ = test_settings.simple_setting.set_value(true, ctx); }); }); app.read(|ctx| { let settings = TestSettings::as_ref(ctx); assert!(settings.simple_setting.is_value_explicitly_set()); assert!(*settings.simple_setting.value()); }); }); } #[test] fn test_explicit_value_tracking_after_clear_value() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); // Register our TestSettings settings group with the app. TestSettings::register(&mut app); // Set a value first app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { let _ = test_settings.simple_setting.set_value(true, ctx); }); }); // Verify it's marked as explicitly set app.read(|ctx| { let settings = TestSettings::as_ref(ctx); assert!(settings.simple_setting.is_value_explicitly_set()); }); // Clear the value app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { let _ = test_settings.simple_setting.clear_value(ctx); }); }); // After clearing, it should no longer be marked as explicitly set app.read(|ctx| { let settings = TestSettings::as_ref(ctx); assert!(!settings.simple_setting.is_value_explicitly_set()); assert_eq!( *settings.simple_setting.value(), SimpleSetting::default_value() ); }); }); } #[test] fn test_explicit_value_tracking_from_storage() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); // Store a value in preferences manually, using the same backend // that new_from_storage will read from. app.update(|ctx| { let prefs = SimpleSetting::preferences_for_setting(ctx); let _ = SimpleSetting::write_to_preferences(&true, prefs); }); // Create a new setting from storage - it should be marked as explicitly set app.update(|ctx| { let setting_from_storage = SimpleSetting::new_from_storage(ctx); assert!(setting_from_storage.is_value_explicitly_set()); assert!(*setting_from_storage.value()); }); // Clear the value from preferences app.update(|ctx| { let prefs = SimpleSetting::preferences_for_setting(ctx); let _ = SimpleSetting::clear_from_preferences(prefs); }); // Create a new setting from storage - it should NOT be marked as explicitly set (no value in storage) app.update(|ctx| { let setting_from_storage = SimpleSetting::new_from_storage(ctx); assert!(!setting_from_storage.is_value_explicitly_set()); assert_eq!( *setting_from_storage.value(), SimpleSetting::default_value() ); }); }); } #[test] fn test_toml_path_returns_some_for_public_setting() { assert_eq!(SimpleSetting::toml_path(), Some("test.simple_setting")); } #[test] fn test_toml_path_returns_none_for_private_setting() { assert_eq!(KeyOverrideSetting::toml_path(), None); } #[test] fn test_storage_key_is_struct_name_not_toml_path() { // storage_key() returns the struct name, not the toml_path segment. // The TOML backend handles snake_case conversion separately. assert_eq!(SimpleSetting::storage_key(), "SimpleSetting"); } #[test] fn test_hierarchy_derived_from_toml_path() { // SimpleSetting has toml_path "test.simple_setting" → hierarchy Some("test") assert_eq!(SimpleSetting::hierarchy(), Some("test")); // HierarchyFlag has toml_path "test_section.hierarchy_flag" → hierarchy Some("test_section") assert_eq!(HierarchyFlag::hierarchy(), Some("test_section")); } #[test] fn test_hierarchy_returns_none_for_private_setting() { assert_eq!(KeyOverrideSetting::hierarchy(), None); } #[test] fn test_private_setting_storage_key_is_explicit_override() { // KeyOverrideSetting has storage_key: "KeyIsOverridden" (no toml_path) assert_eq!(KeyOverrideSetting::storage_key(), "KeyIsOverridden"); } #[test] fn test_load_value_updates_value_without_persisting() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Load a non-default value. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings .simple_setting .load_value(true, true, ctx) .unwrap(); }); }); // In-memory value should be updated. app.read(|ctx| { assert!(*TestSettings::as_ref(ctx).simple_setting.value()); }); // Storage should still be empty — load_value must not persist. app.read(|ctx| { let prefs = SimpleSetting::preferences_for_setting(ctx); let stored = prefs.read_value(SimpleSetting::storage_key()).unwrap(); assert!(stored.is_none(), "load_value should not write to storage"); }); }); } #[test] fn test_load_value_emits_event_on_change() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); let handle = app.add_model(EventListener::new); app.read(|ctx| { assert!(!handle.as_ref(ctx).got_event); }); app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings .simple_setting .load_value(true, true, ctx) .unwrap(); }); }); app.read(|ctx| { assert!( handle.as_ref(ctx).got_event, "load_value should emit event when value changes" ); }); }); } #[test] fn test_load_value_skips_event_when_unchanged() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); let handle = app.add_model(EventListener::new); // Load the same value as the default (false) with explicitly_set=false // (matching the initial state). app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings .simple_setting .load_value(false, false, ctx) .unwrap(); }); }); app.read(|ctx| { assert!( !handle.as_ref(ctx).got_event, "load_value should not emit event when value and explicitly_set are unchanged" ); }); }); } #[test] fn test_load_value_updates_explicitly_set_flag() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Initially not explicitly set. app.read(|ctx| { assert!( !TestSettings::as_ref(ctx) .simple_setting .is_value_explicitly_set() ); }); // Load with explicitly_set=true. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings .simple_setting .load_value(true, true, ctx) .unwrap(); }); }); app.read(|ctx| { assert!( TestSettings::as_ref(ctx) .simple_setting .is_value_explicitly_set() ); }); }); } #[test] fn test_load_value_resets_explicitly_set_flag() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // First, explicitly set a value. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings.simple_setting.set_value(true, ctx).unwrap(); }); }); app.read(|ctx| { assert!( TestSettings::as_ref(ctx) .simple_setting .is_value_explicitly_set() ); }); // Load back the default with explicitly_set=false (simulates key removed from file). app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings .simple_setting .load_value(SimpleSetting::default_value(), false, ctx) .unwrap(); }); }); app.read(|ctx| { let settings = TestSettings::as_ref(ctx); assert!(!settings.simple_setting.is_value_explicitly_set()); assert_eq!( *settings.simple_setting.value(), SimpleSetting::default_value() ); }); }); } #[test] fn test_explicit_value_tracking_cloud_sync() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); // Register our TestSettings settings group with the app. TestSettings::register(&mut app); // Initially not explicitly set app.read(|ctx| { let settings = TestSettings::as_ref(ctx); assert!(!settings.simple_setting.is_value_explicitly_set()); }); // Set value from cloud sync app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { let _ = test_settings .simple_setting .set_value_from_cloud_sync(true, ctx); }); }); // Should now be marked as explicitly set app.read(|ctx| { let settings = TestSettings::as_ref(ctx); assert!(settings.simple_setting.is_value_explicitly_set()); assert!(*settings.simple_setting.value()); }); }); } mod file_transform_tests { use settings_value::SettingsValue; #[test] fn test_vec_u32_uses_serde_passthrough() { // Vec uses the default SettingsValue (serde passthrough) let value = vec![10u32, 20, 30]; let file_val = value.to_file_value(); assert_eq!(file_val, serde_json::json!([10, 20, 30])); let back = Vec::::from_file_value(&file_val).unwrap(); assert_eq!(back, value); } #[test] fn test_bool_settings_value() { assert_eq!(true.to_file_value(), serde_json::json!(true)); assert_eq!( bool::from_file_value(&serde_json::json!(false)), Some(false) ); } } // --------------------------------------------------------------------------- // Private / public settings split tests // --------------------------------------------------------------------------- #[test] fn test_is_private_returns_false_for_public_setting() { assert!(!SimpleSetting::is_private()); } #[test] fn test_is_private_returns_true_for_private_setting() { assert!(KeyOverrideSetting::is_private()); } #[test] fn test_public_setting_writes_to_public_prefs_when_flag_enabled() { let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(true); galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Set the public setting to a non-default value. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings.simple_setting.set_value(true, ctx).unwrap(); }); }); // The value should be in the public (Model) backend. app.read(|ctx| { let public = ::as_ref(ctx); let stored = public .as_preferences() .read_value(SimpleSetting::storage_key()) .unwrap(); assert!(stored.is_some(), "public setting should be in public prefs"); }); // The value should NOT be in the private backend. app.read(|ctx| { let private = ::as_ref(ctx); let stored = private.0.read_value(SimpleSetting::storage_key()).unwrap(); assert!( stored.is_none(), "public setting should not be in private prefs" ); }); }); } #[test] fn test_private_setting_writes_to_private_prefs_when_flag_enabled() { let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(true); galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Set the private setting to a non-default value. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings .key_override_setting .set_value(false, ctx) .unwrap(); }); }); // The value should be in the private backend. app.read(|ctx| { let private = ::as_ref(ctx); let stored = private .0 .read_value(KeyOverrideSetting::storage_key()) .unwrap(); assert!( stored.is_some(), "private setting should be in private prefs" ); }); // The value should NOT be in the public backend. app.read(|ctx| { let public = ::as_ref(ctx); let stored = public .as_preferences() .read_value(KeyOverrideSetting::storage_key()) .unwrap(); assert!( stored.is_none(), "private setting should not be in public prefs" ); }); }); } #[test] fn test_new_from_storage_reads_from_correct_backend_when_flag_enabled() { let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(true); galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Write a value directly to the public backend for the public setting. app.update(|ctx| { let public = ::as_ref(ctx); public .as_preferences() .write_value(SimpleSetting::storage_key(), "true".to_string()) .unwrap(); }); // Write a value directly to the private backend for the private setting. app.update(|ctx| { let private = ::as_ref(ctx); private .0 .write_value(KeyOverrideSetting::storage_key(), "false".to_string()) .unwrap(); }); // new_from_storage should read from the correct backends. app.update(|ctx| { let public_setting = SimpleSetting::new_from_storage(ctx); assert!(*public_setting.value()); let private_setting = KeyOverrideSetting::new_from_storage(ctx); assert!(!*private_setting.value()); }); }); } #[test] fn test_clear_value_clears_from_correct_backend() { let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(true); galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Set both settings. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings.simple_setting.set_value(true, ctx).unwrap(); test_settings .key_override_setting .set_value(false, ctx) .unwrap(); }); }); // Clear both settings. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings.simple_setting.clear_value(ctx).unwrap(); test_settings.key_override_setting.clear_value(ctx).unwrap(); }); }); // Public backend should no longer have the public setting. app.read(|ctx| { let public = ::as_ref(ctx); assert!( public .as_preferences() .read_value(SimpleSetting::storage_key()) .unwrap() .is_none(), "cleared public setting should not be in public prefs" ); }); // Private backend should no longer have the private setting. app.read(|ctx| { let private = ::as_ref(ctx); assert!( private .0 .read_value(KeyOverrideSetting::storage_key()) .unwrap() .is_none(), "cleared private setting should not be in private prefs" ); }); }); } #[test] fn test_public_setting_uses_private_prefs_when_flag_disabled() { let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(false); galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Set the public setting. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings.simple_setting.set_value(true, ctx).unwrap(); }); }); // With the flag disabled, public settings fall back to the private backend. app.read(|ctx| { let private = ::as_ref(ctx); let stored = private.0.read_value(SimpleSetting::storage_key()).unwrap(); assert!( stored.is_some(), "public setting should be in private prefs when flag is disabled" ); }); // The public backend should NOT have it. app.read(|ctx| { let public = ::as_ref(ctx); let stored = public .as_preferences() .read_value(SimpleSetting::storage_key()) .unwrap(); assert!( stored.is_none(), "public setting should not be in public prefs when flag is disabled" ); }); }); } #[test] fn test_private_setting_uses_private_prefs_when_flag_disabled() { let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(false); galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Set the private setting. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings .key_override_setting .set_value(false, ctx) .unwrap(); }); }); // Private settings always go to the private backend. app.read(|ctx| { let private = ::as_ref(ctx); let stored = private .0 .read_value(KeyOverrideSetting::storage_key()) .unwrap(); assert!( stored.is_some(), "private setting should be in private prefs when flag is disabled" ); }); }); } #[test] fn test_new_from_storage_reads_from_private_backend_when_flag_disabled() { let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(false); galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Write values directly to the private backend for both settings. app.update(|ctx| { let private = ::as_ref(ctx); private .0 .write_value(SimpleSetting::storage_key(), "true".to_string()) .unwrap(); private .0 .write_value(KeyOverrideSetting::storage_key(), "false".to_string()) .unwrap(); }); // Both settings should read from the private backend. app.update(|ctx| { let public_setting = SimpleSetting::new_from_storage(ctx); assert!( *public_setting.value(), "public setting should read from private prefs when flag is disabled" ); let private_setting = KeyOverrideSetting::new_from_storage(ctx); assert!( !*private_setting.value(), "private setting should read from private prefs when flag is disabled" ); }); }); } // --------------------------------------------------------------------------- // SettingsManager private/public routing tests // --------------------------------------------------------------------------- #[test] fn test_manager_is_private_for_storage_key() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); app.read(|ctx| { let manager = SettingsManager::as_ref(ctx); assert!( !manager.is_private_for_storage_key("SimpleSetting"), "SimpleSetting should not be private" ); assert!( manager.is_private_for_storage_key("KeyIsOverridden"), "KeyIsOverridden should be private" ); assert!( !manager.is_private_for_storage_key("UnknownKey"), "unknown key should default to not private" ); }); }); } #[test] fn test_manager_default_values_for_settings_file_excludes_private() { galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); app.read(|ctx| { let manager = SettingsManager::as_ref(ctx); let keys: Vec<&str> = manager .default_values_for_settings_file() .map(|(key, _, _, _)| key) .collect(); assert!( keys.contains(&"simple_setting"), "public setting should appear in settings file defaults" ); assert!( keys.contains(&"hierarchy_flag"), "public hierarchy setting should appear in settings file defaults" ); assert!( !keys.contains(&"KeyIsOverridden"), "private setting should not appear in settings file defaults" ); }); }); } #[test] fn test_manager_read_local_setting_value_routes_when_flag_enabled() { let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(true); galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Write directly to the correct backends. app.update(|ctx| { let public = ::as_ref(ctx); public .as_preferences() .write_value("SimpleSetting", "true".to_string()) .unwrap(); let private = ::as_ref(ctx); private .0 .write_value("KeyIsOverridden", "false".to_string()) .unwrap(); }); app.read(|ctx| { let manager = SettingsManager::as_ref(ctx); let public_val = manager .read_local_setting_value("SimpleSetting", ctx) .unwrap(); assert_eq!( public_val, Some("true".to_string()), "manager should read public setting from public backend" ); let private_val = manager .read_local_setting_value("KeyIsOverridden", ctx) .unwrap(); assert_eq!( private_val, Some("false".to_string()), "manager should read private setting from private backend" ); }); }); } #[test] fn test_manager_read_local_setting_value_falls_back_when_flag_disabled() { let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(false); galaxyui_core::App::test((), |mut app| async move { app.update(init_and_register_preferences); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Write both values to the private backend. app.update(|ctx| { let private = ::as_ref(ctx); private .0 .write_value("SimpleSetting", "true".to_string()) .unwrap(); private .0 .write_value("KeyIsOverridden", "false".to_string()) .unwrap(); }); app.read(|ctx| { let manager = SettingsManager::as_ref(ctx); let public_val = manager .read_local_setting_value("SimpleSetting", ctx) .unwrap(); assert_eq!( public_val, Some("true".to_string()), "public setting should fall back to private backend" ); let private_val = manager .read_local_setting_value("KeyIsOverridden", ctx) .unwrap(); assert_eq!( private_val, Some("false".to_string()), "private setting should read from private backend" ); }); }); } /// Regression test for the settings sync disappearing on restart bug: when /// the TOML settings file is enabled, `read_local_setting_value` must /// forward the setting's hierarchy, otherwise values stored under a /// section like `[account]` are invisible to the SettingsManager and the /// cloud preferences syncer clobbers them with stale cloud state. #[test] fn test_manager_read_local_setting_value_respects_hierarchy_with_settings_file() { use galaxyui_extras::user_preferences::toml_backed::TomlBackedUserPreferences; let _guard = galaxy_features::FeatureFlag::SettingsFile.override_enabled(true); let dir = tempfile::tempdir().unwrap(); let file_path = dir.path().join("settings.toml"); galaxyui_core::App::test((), |mut app| async move { // Use the TOML-backed store for public preferences so the hierarchy // routing actually matters; in-memory preferences ignore hierarchy // entirely and would hide this bug. let file_path_for_public = file_path.clone(); app.add_singleton_model(move |_| { let (prefs, _) = TomlBackedUserPreferences::new(file_path_for_public); crate::PublicPreferences::new(Box::new(prefs)) }); app.add_singleton_model(|_| { crate::PrivatePreferences::new(Box::< galaxyui_extras::user_preferences::in_memory::InMemoryPreferences, >::default()) }); app.add_singleton_model(|_| SettingsManager::default()); TestSettings::register(&mut app); // Toggle a public, hierarchy-scoped setting via the normal write // path. `set_value` writes to the TOML under `[test_section]`. app.update(|ctx| { TestSettings::handle(ctx).update(ctx, |test_settings, ctx| { test_settings.hierarchy_flag.set_value(true, ctx).unwrap(); }); }); // The SettingsManager should see the value we just wrote. Without // the fix, this returned None because the read path looked at the // root table instead of `[test_section]`. app.read(|ctx| { let manager = SettingsManager::as_ref(ctx); let value = manager .read_local_setting_value("HierarchyFlag", ctx) .unwrap(); assert_eq!( value, Some("true".to_string()), "SettingsManager must forward hierarchy when reading from \ the TOML-backed store" ); }); }); }