(Init): Added shit

This commit is contained in:
2026-05-29 00:41:12 +00:00
commit 72005fd71d
52 changed files with 12875 additions and 0 deletions

311
tests/test_features.py Normal file
View File

@@ -0,0 +1,311 @@
#!/usr/bin/env python3
"""Headless feature tests for NiriMod — exercises every page's logic."""
import sys
import traceback
import os
os.environ.setdefault("DISPLAY", ":0")
os.environ.setdefault("WAYLAND_DISPLAY", "wayland-1")
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
PASS = "[PASS]"
WARN = "[WARN]"
FAIL = "[FAIL]"
results = []
def test(name, fn):
try:
msg = fn()
results.append((PASS, name, msg or ""))
except Exception as e:
results.append((FAIL, name, f"{type(e).__name__}: {e}"))
traceback.print_exc()
test.__test__ = False
# KDL Parser
from nirimod.kdl_parser import parse_kdl, write_kdl, KdlNode
def t_kdl_roundtrip():
src = 'output "eDP-1" { scale 2.0; }\nbinds { XF86AudioRaise { action "volume-up"; } }'
nodes = parse_kdl(src)
assert nodes, "no nodes parsed"
return f"{len(nodes)} nodes"
def t_kdl_include():
src = 'include "~/.config/niri/dms/monitor.kdl"\nspawn-at-startup "waybar"'
nodes = parse_kdl(src)
names = [n.name for n in nodes]
assert "include" in names
assert "spawn-at-startup" in names
return "include + spawn parsed"
def t_kdl_nested():
src = 'window-rule { match app-id="firefox"; open-maximized true; }'
nodes = parse_kdl(src)
assert nodes[0].name == "window-rule"
assert nodes[0].children
return "nested nodes OK"
def t_kdl_write():
node = KdlNode("spawn-at-startup", args=["waybar", "--config", "/etc/waybar.json"])
out = write_kdl([node])
assert "waybar" in out
return out.strip()
test("KDL: parse + write roundtrip", t_kdl_roundtrip)
test("KDL: include directive parsing", t_kdl_include)
test("KDL: nested children parsing", t_kdl_nested)
test("KDL: write KdlNode with args", t_kdl_write)
# AppState
from nirimod.state import AppState
def t_state_load():
s = AppState()
s.load()
assert isinstance(s.nodes, list)
return f"{len(s.nodes)} top-level nodes loaded"
def t_state_dirty():
s = AppState()
s.load()
assert not s.is_dirty
s.mark_dirty()
assert s.is_dirty
s.mark_clean()
assert not s.is_dirty
return "dirty/clean flags OK"
def t_state_discard():
s = AppState()
s.load()
original_len = len(s.nodes)
s.nodes.append(KdlNode("test-node"))
s.mark_dirty()
s.discard()
assert len(s.nodes) == original_len
return f"discarded back to {original_len} nodes"
def t_state_undo():
s = AppState()
s.load()
before = write_kdl(s.nodes)
s.nodes.append(KdlNode("test-undo-node"))
after = write_kdl(s.nodes)
s.push_undo("add test node", before, after)
assert s.undo.can_undo()
entry = s.apply_undo()
assert entry is not None
assert "test-undo-node" not in write_kdl(s.nodes)
return "undo restored previous state"
test("AppState: load from disk", t_state_load)
test("AppState: dirty / clean flags", t_state_dirty)
test("AppState: discard reverts nodes", t_state_discard)
test("AppState: undo stack push+pop", t_state_undo)
# Undo Manager
from nirimod.undo import UndoManager, UndoEntry
def t_undo_redo():
m = UndoManager()
m.push(UndoEntry("step1", "before1", "after1"))
m.push(UndoEntry("step2", "before2", "after2"))
assert m.can_undo()
e = m.pop_undo()
assert e.description == "step2"
assert m.can_redo()
e2 = m.pop_redo()
assert e2.description == "step2"
return "undo→redo cycle OK"
test("UndoManager: push/pop/redo", t_undo_redo)
# Profiles
from nirimod import profiles as prof_mod
def t_profiles_list():
names = prof_mod.list_profiles()
assert isinstance(names, list)
return f"{len(names)} profiles found"
def t_profiles_save_delete():
s = AppState()
s.load()
# save_profile takes name + optional set[Path] of source files
prof_mod.save_profile("__test_profile__", s.source_files)
names = prof_mod.list_profiles()
assert "__test_profile__" in names, f"profile not found in {names}"
prof_mod.delete_profile("__test_profile__")
assert "__test_profile__" not in prof_mod.list_profiles()
return "save + delete profile OK"
test("Profiles: list", t_profiles_list)
test("Profiles: save and delete", t_profiles_save_delete)
# Pages (import + build check)
# We test imports and logic only — no GTK widget creation without display
page_modules = [
("appearance", "nirimod.pages.appearance"),
("animations", "nirimod.pages.animations"),
("layout", "nirimod.pages.layout"),
("startup", "nirimod.pages.startup"),
("environment","nirimod.pages.environment"),
("workspaces", "nirimod.pages.workspaces"),
("window_rules","nirimod.pages.window_rules"),
("bindings", "nirimod.pages.bindings"),
("outputs", "nirimod.pages.outputs"),
("input_page", "nirimod.pages.input_page"),
("gestures", "nirimod.pages.gestures"),
("raw_config", "nirimod.pages.raw_config"),
]
import importlib
for name, module_path in page_modules:
def _test(mp=module_path, n=name):
importlib.import_module(mp)
return "module imported OK"
test(f"Page import: {name}", _test)
# Startup page logic
import shlex
def t_startup_spawn_sh():
cmd = "waybar --config /etc/waybar.json"
node = KdlNode("spawn-sh-at-startup", args=[cmd]) # single string for sh
assert node.args[0] == cmd
return f"spawn-sh-at-startup args = {node.args}"
def t_startup_spawn_direct():
cmd = "dunst"
args = shlex.split(cmd)
node = KdlNode("spawn-at-startup", args=args)
assert node.args == ["dunst"]
return "spawn-at-startup args OK"
test("Startup: spawn-sh-at-startup node", t_startup_spawn_sh)
test("Startup: spawn-at-startup node", t_startup_spawn_direct)
# Animations curve serialization
def t_anim_curve_format():
# The correct niri format is: curve "cubic-bezier" 0.25 0.1 0.25 1.0
kdl = 'animations { workspace-switch { spring damping-ratio=1.0; } }'
nodes = parse_kdl(kdl)
out = write_kdl(nodes)
assert "workspace-switch" in out
return "animation node roundtrip OK"
test("Animations: curve node roundtrip", t_anim_curve_format)
# Environment page logic
def t_env_node():
node = KdlNode("environment", children=[
KdlNode("WAYLAND_DISPLAY", args=["wayland-1"])
])
out = write_kdl([node])
assert "WAYLAND_DISPLAY" in out
return out.strip()
test("Environment: env var node write", t_env_node)
# Window rules logic
def t_window_rule_node():
kdl = 'window-rule { match app-id="org.gnome.Calculator"; open-floating true; }'
nodes = parse_kdl(kdl)
assert nodes[0].name == "window-rule"
children_names = [c.name for c in nodes[0].children]
assert "match" in children_names
assert "open-floating" in children_names
return f"children: {children_names}"
test("Window Rules: parse match+action", t_window_rule_node)
# Output node
def t_output_node():
kdl = 'output "eDP-1" { scale 1.5; transform "90"; mode "1920x1080@60"; }'
nodes = parse_kdl(kdl)
assert nodes[0].name == "output"
assert nodes[0].args == ["eDP-1"]
children = {c.name: c for c in nodes[0].children}
assert "scale" in children
assert float(children["scale"].args[0]) == 1.5
return "output node parsed OK"
test("Outputs: parse output node", t_output_node)
# Bindings logic
def t_binds_node():
kdl = 'binds { Mod+T { action spawn "alacritty"; } Mod+Q { action close-window; } }'
nodes = parse_kdl(kdl)
assert nodes[0].name == "binds"
assert len(nodes[0].children) == 2
return f"{len(nodes[0].children)} binds found"
test("Bindings: parse binds block", t_binds_node)
# Workspaces logic
def t_workspaces_node():
kdl = 'workspaces { workspace "Browser"; workspace "Terminal"; }'
nodes = parse_kdl(kdl)
assert nodes[0].name == "workspaces"
return f"{len(nodes[0].children)} workspaces"
test("Workspaces: parse workspace names", t_workspaces_node)
# NiriIPC
from nirimod import niri_ipc
def t_ipc_is_running():
result = niri_ipc.is_niri_running()
assert isinstance(result, bool)
return f"niri running = {result}"
def t_ipc_has_touchpad():
result = niri_ipc.has_touchpad()
assert isinstance(result, bool)
return f"has touchpad = {result}"
test("NiriIPC: is_niri_running()", t_ipc_is_running)
test("NiriIPC: has_touchpad()", t_ipc_has_touchpad)
# AppSettings
from nirimod import app_settings
def t_app_settings():
original = app_settings.get("auto_update", True)
app_settings.set("auto_update", False)
assert not app_settings.get("auto_update")
app_settings.set("auto_update", original)
return "get/set OK"
test("AppSettings: get/set", t_app_settings)
def _print_results() -> int:
print("\n" + "="*50)
print(" NIRIMOD FEATURE TEST REPORT")
print("="*50)
passed = sum(1 for r in results if r[0] == PASS)
failed = sum(1 for r in results if r[0] == FAIL)
warned = sum(1 for r in results if r[0] == WARN)
for icon, name, detail in results:
status = f"{icon} {name}"
if detail:
print(f"{status}\n{detail}")
else:
print(status)
print("="*50)
print(
f" {passed} passed | {warned} warnings | {failed} failed | {len(results)} total"
)
print("="*50)
return failed
if __name__ == "__main__":
sys.exit(1 if _print_results() else 0)