srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/config/src/engine/tests.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-07-29 14:01:00 +0200
committersrdusr <[email protected]>2024-07-29 14:01:00 +0200
commit5fe599437ab9d1d9cfa749e861ce2f3acc02aa3c (patch)
tree2956b9b18af7b6c0741da8b68da18de66ed393e5 /crates/config/src/engine/tests.rs
parent6fad44b3ecffb22f77a78661ce179b304e9a6ccc (diff)
downloadsrdwm-5fe599437ab9d1d9cfa749e861ce2f3acc02aa3c.tar.gz
srdwm-5fe599437ab9d1d9cfa749e861ce2f3acc02aa3c.zip
Split crates/config/src/lib.rs (1441 lines) into engine/
Pure reorganization, no behavior change - verified by diffing the function-name and struct/enum-name sets before/after (both identical) plus a full cargo test pass. lib.rs is now a thin shim (mod declarations + pub use) since a crate root can't itself become a directory; all the actual content moved into engine/, split along the Lua API's own srd.*/srd.window.*/srd.layout.*/srd.workspace.*/ srd.theme.* namespace groupings the file's own section comments already used: - mod.rs: SharedState, Engine, ConfigError, and Engine's core methods (new/get/set/dispatch/reload/...). - register.rs: register_srd_module, which wires every fn_* builder from every other file into the srd Lua table - the one place that genuinely needs to see all of them. - general.rs/window.rs/layout.rs/workspace.rs/theme.rs: the fn_* builder methods themselves, one file per srd.* sub-namespace. - support.rs: free functions shared across those (do_reload, parse_direction, flatten_table_into, validate, default_config) and the WindowAction enum. - tests.rs: the ~300-line test module, left unsplit for the same shared-helper reason manager/tests.rs and udev's tests were. ~40 fn_* methods and the support.rs free functions/enum went from private to pub(super): called across what are now sibling submodules, which Rust's privacy model doesn't let see each other's private items.
Diffstat (limited to 'crates/config/src/engine/tests.rs')
-rw-r--r--crates/config/src/engine/tests.rs316
1 files changed, 316 insertions, 0 deletions
diff --git a/crates/config/src/engine/tests.rs b/crates/config/src/engine/tests.rs
new file mode 100644
index 0000000..521dc5a
--- /dev/null
+++ b/crates/config/src/engine/tests.rs
@@ -0,0 +1,316 @@
+ use super::*;
+ use srdwm_core::Window;
+
+ fn engine_in(dir: &std::path::Path) -> Engine {
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ Engine::new(wm, dir).unwrap()
+ }
+
+ #[test]
+ fn srd_set_and_get_roundtrip_scalars() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine.lua.load(r#"srd.set("general.window_gap", 12)"#).exec().unwrap();
+ assert_eq!(engine.get("general.window_gap"), Some(ConfigValue::Number(12.0)));
+ }
+
+ #[test]
+ fn defaults_are_seeded_before_any_script_runs() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ assert_eq!(engine.get_string("general.default_layout", ""), "dynamic");
+ }
+
+ #[test]
+ fn reset_restores_default_value() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine.lua.load(r#"srd.set("general.window_gap", 99)"#).exec().unwrap();
+ engine.lua.load(r#"srd.reset("general.window_gap")"#).exec().unwrap();
+ assert_eq!(engine.get("general.window_gap"), Some(ConfigValue::Number(8.0)));
+ }
+
+ #[test]
+ fn bind_stores_real_closure_and_dispatch_runs_it() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine
+ .lua
+ .load(r#"srd.bind("Mod4+q", function() srd.set("test.marker", true) end)"#)
+ .exec()
+ .unwrap();
+ assert!(engine.dispatch_keybinding("Mod4+q"));
+ assert_eq!(engine.get("test.marker"), Some(ConfigValue::Bool(true)));
+ assert!(!engine.dispatch_keybinding("Mod4+nonexistent"));
+ }
+
+ #[test]
+ fn srd_is_requireable_not_just_a_global() {
+ // Every shipped example config opens with `local srd = require("srd")`;
+ // that must resolve through package.preload, not just exist as a global.
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine
+ .lua
+ .load(r#"local srd = require("srd"); srd.set("test.via_require", true)"#)
+ .exec()
+ .unwrap();
+ assert_eq!(engine.get("test.via_require"), Some(ConfigValue::Bool(true)));
+ }
+
+ #[test]
+ fn window_close_style_call_from_legacy_example_config_now_works() {
+ // The legacy C++ engine's `srd.window.focused()` returned a
+ // placeholder table with no methods, so `window:close()` in the
+ // shipped example config would have errored at runtime. Here
+ // `srd.window.close()` acts directly on the focused window.
+ let dir = tempfile::tempdir().unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ {
+ let mut wm = wm.borrow_mut();
+ let id = wm.alloc_window_id();
+ wm.add_window(Window::new(id, "test"));
+ }
+ let engine = Engine::new(wm.clone(), dir.path()).unwrap();
+ engine
+ .lua
+ .load(
+ r#"
+ local w = srd.window.focused()
+ assert(w ~= nil, "expected a focused window")
+ srd.window.set_floating(true)
+ assert(srd.window.is_floating() == true)
+ "#,
+ )
+ .exec()
+ .unwrap();
+ }
+
+ #[test]
+ fn layout_configure_updates_master_ratio_live() {
+ let dir = tempfile::tempdir().unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ let engine = Engine::new(wm.clone(), dir.path()).unwrap();
+ engine
+ .lua
+ .load(r#"srd.layout.configure("tiling", { master_ratio = 0.75 })"#)
+ .exec()
+ .unwrap();
+ assert_eq!(engine.get("layout.tiling.master_ratio"), Some(ConfigValue::Number(0.75)));
+ assert!((wm.borrow().tiling.master_ratio - 0.75).abs() < f32::EPSILON);
+ }
+
+ #[test]
+ fn srd_load_executes_module_relative_to_config_dir() {
+ let dir = tempfile::tempdir().unwrap();
+ std::fs::write(dir.path().join("extra.lua"), r#"srd.set("from.extra", "yes")"#).unwrap();
+ let engine = engine_in(dir.path());
+ engine.lua.load(r#"srd.load("extra")"#).exec().unwrap();
+ assert_eq!(engine.get("from.extra"), Some(ConfigValue::String("yes".into())));
+ }
+
+ #[test]
+ fn validate_config_passes_on_untouched_defaults() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine
+ .lua
+ .load(r#"local ok, errs = srd.validate_config(); assert(ok, table.concat(errs, "; "))"#)
+ .exec()
+ .unwrap();
+ }
+
+ #[test]
+ fn validate_config_flags_out_of_range_gap_and_bad_color() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine
+ .lua
+ .load(
+ r#"
+ srd.set("general.window_gap", 500)
+ srd.set("theme.colors.background", "not-a-color")
+ local ok, errs = srd.validate_config()
+ assert(ok == false)
+ assert(#errs == 2, "expected 2 errors, got " .. #errs)
+ "#,
+ )
+ .exec()
+ .unwrap();
+ }
+
+ #[test]
+ fn validate_config_flags_unregistered_layout_name() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine
+ .lua
+ .load(
+ r#"
+ srd.set("general.default_layout", "nonexistent")
+ local ok, errs = srd.validate_config()
+ assert(ok == false)
+ "#,
+ )
+ .exec()
+ .unwrap();
+ }
+
+ #[test]
+ fn debug_namespace_reports_status_and_profiles() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine
+ .lua
+ .load(
+ r#"
+ local status = srd.debug.config_status()
+ assert(status.keys > 0)
+ srd.debug.profile_start()
+ local elapsed = srd.debug.profile_stop()
+ assert(type(elapsed) == "number")
+ local settings = srd.debug.show_settings()
+ assert(settings["general.window_gap"] == 8)
+ "#,
+ )
+ .exec()
+ .unwrap();
+ }
+
+ #[test]
+ fn srd_rule_floats_matching_window_on_creation() {
+ let dir = tempfile::tempdir().unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ let engine = Engine::new(wm.clone(), dir.path()).unwrap();
+ engine
+ .lua
+ .load(r#"srd.rule({ title = "calculator" }, { floating = true })"#)
+ .exec()
+ .unwrap();
+ let id = {
+ let mut wm = wm.borrow_mut();
+ let id = wm.alloc_window_id();
+ wm.add_window(srdwm_core::Window::new(id, "Calculator"));
+ id
+ };
+ assert!(wm.borrow().is_floating(id));
+ }
+
+ #[test]
+ fn srd_window_scratchpad_hides_the_focused_window_and_show_brings_it_back() {
+ let dir = tempfile::tempdir().unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ let id = {
+ let mut wm = wm.borrow_mut();
+ let id = wm.alloc_window_id();
+ wm.add_window(Window::new(id, "term"));
+ id
+ };
+ let engine = Engine::new(wm.clone(), dir.path()).unwrap();
+ engine.lua.load(r#"srd.window.scratchpad()"#).exec().unwrap();
+ assert!(wm.borrow().window(id).unwrap().minimized);
+ assert!(wm.borrow().window(id).unwrap().scratchpad);
+ engine.lua.load(r#"srd.window.scratchpad_show()"#).exec().unwrap();
+ assert!(!wm.borrow().window(id).unwrap().minimized);
+ assert_eq!(wm.borrow().focused_id(), Some(id));
+ }
+
+ #[test]
+ fn srd_rule_title_regex_matches_a_specific_dialog_not_the_main_window() {
+ let dir = tempfile::tempdir().unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ let engine = Engine::new(wm.clone(), dir.path()).unwrap();
+ engine.lua.load(r#"srd.rule({ title_regex = "^Save File$" }, { floating = true })"#).exec().unwrap();
+ let dialog = {
+ let mut wm = wm.borrow_mut();
+ let id = wm.alloc_window_id();
+ wm.add_window(srdwm_core::Window::new(id, "Save File"));
+ id
+ };
+ let main = {
+ let mut wm = wm.borrow_mut();
+ let id = wm.alloc_window_id();
+ wm.add_window(srdwm_core::Window::new(id, "Save File - GIMP"));
+ id
+ };
+ assert!(wm.borrow().is_floating(dialog));
+ assert!(!wm.borrow().is_floating(main));
+ }
+
+ #[test]
+ fn srd_rule_instance_matches_independently_of_class() {
+ let dir = tempfile::tempdir().unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ let engine = Engine::new(wm.clone(), dir.path()).unwrap();
+ engine.lua.load(r#"srd.rule({ instance = "firefox" }, { pinned = true })"#).exec().unwrap();
+ let id = {
+ let mut wm = wm.borrow_mut();
+ let id = wm.alloc_window_id();
+ let mut w = srdwm_core::Window::new(id, "Mozilla Firefox");
+ w.app_id = "Navigator".into();
+ w.instance = "firefox".into();
+ wm.add_window(w);
+ id
+ };
+ assert!(wm.borrow().window(id).unwrap().always_on_top);
+ }
+
+ #[test]
+ fn srd_rule_rejects_an_invalid_regex_with_a_lua_error() {
+ let dir = tempfile::tempdir().unwrap();
+ let wm = Rc::new(RefCell::new(WindowManager::new()));
+ let engine = Engine::new(wm.clone(), dir.path()).unwrap();
+ let result = engine.lua.load(r#"srd.rule({ title_regex = "(unclosed" }, { floating = true })"#).exec();
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn load_init_runs_the_users_init_lua() {
+ let dir = tempfile::tempdir().unwrap();
+ std::fs::write(dir.path().join("init.lua"), r#"srd.set("general.window_gap", 4)"#).unwrap();
+ let engine = engine_in(dir.path());
+ engine.load_init().unwrap();
+ assert_eq!(engine.get("general.window_gap"), Some(ConfigValue::Number(4.0)));
+ }
+
+ #[test]
+ fn bind_repeat_registers_the_binding_and_marks_it_repeating() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine
+ .lua
+ .load(r#"
+ srd.bind("Mod4+a", function() end)
+ srd.bind_repeat("XF86AudioRaiseVolume", function() end)
+ "#)
+ .exec()
+ .unwrap();
+
+ let bound = engine.bound_keys();
+ // A repeating bind is still a normal binding - it must be grabbed
+ // and dispatched like any other, or it would never fire at all.
+ assert!(bound.contains(&"Mod4+a".to_string()));
+ assert!(bound.contains(&"XF86AudioRaiseVolume".to_string()));
+
+ let repeat = engine.repeat_keys();
+ assert_eq!(repeat, vec!["XF86AudioRaiseVolume".to_string()]);
+ assert!(!repeat.contains(&"Mod4+a".to_string()), "a plain bind must not repeat");
+ }
+
+ #[test]
+ fn bind_repeat_dispatches_like_a_normal_binding() {
+ let dir = tempfile::tempdir().unwrap();
+ let engine = engine_in(dir.path());
+ engine
+ .lua
+ .load(r#"
+ fired = 0
+ srd.bind_repeat("Mod4+z", function() fired = fired + 1 end)
+ "#)
+ .exec()
+ .unwrap();
+ assert!(engine.dispatch_keybinding("Mod4+z"));
+ assert!(engine.dispatch_keybinding("Mod4+z"));
+ let fired: i64 = engine.lua.globals().get("fired").unwrap();
+ assert_eq!(fired, 2);
+ }