163 lines
5.1 KiB
Python
163 lines
5.1 KiB
Python
"""Unit tests for the AppState manager.
|
|
|
|
Tests state initialization, dirty tracking, undo/redo integration,
|
|
commit_save, discard, and node serialization helpers — without requiring
|
|
a live GTK session or filesystem access.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from nirimod.kdl_parser import KdlNode, KdlRawString, parse_kdl, write_kdl
|
|
from nirimod.state import AppState
|
|
|
|
|
|
class TestAppStateInit(unittest.TestCase):
|
|
"""AppState starts in a clean, non-dirty state."""
|
|
|
|
def test_initial_state(self):
|
|
state = AppState()
|
|
self.assertEqual(state.nodes, [])
|
|
self.assertEqual(state.saved_kdl, "")
|
|
self.assertFalse(state.is_dirty)
|
|
self.assertFalse(state.niri_running)
|
|
self.assertFalse(state.has_touchpad)
|
|
|
|
def test_initial_undo_empty(self):
|
|
state = AppState()
|
|
self.assertFalse(state.undo.can_undo())
|
|
self.assertFalse(state.undo.can_redo())
|
|
|
|
|
|
class TestDirtyTracking(unittest.TestCase):
|
|
def test_mark_dirty(self):
|
|
state = AppState()
|
|
self.assertFalse(state.is_dirty)
|
|
state.mark_dirty()
|
|
self.assertTrue(state.is_dirty)
|
|
|
|
def test_mark_clean(self):
|
|
state = AppState()
|
|
state.mark_dirty()
|
|
state.mark_clean()
|
|
self.assertFalse(state.is_dirty)
|
|
|
|
|
|
class TestUndoRedo(unittest.TestCase):
|
|
def _make_state_with_nodes(self, kdl: str) -> AppState:
|
|
state = AppState()
|
|
state.nodes = parse_kdl(kdl)
|
|
state._saved_kdl = kdl
|
|
return state
|
|
|
|
def test_push_and_apply_undo(self):
|
|
state = self._make_state_with_nodes("gaps 8\n")
|
|
before = "gaps 8\n"
|
|
after = "gaps 16\n"
|
|
state.push_undo("change gaps", before, after)
|
|
self.assertTrue(state.undo.can_undo())
|
|
|
|
entry = state.apply_undo()
|
|
self.assertIsNotNone(entry)
|
|
self.assertEqual(state.nodes[0].get_child("gaps") if state.nodes and state.nodes[0].children else None, None)
|
|
# After undo, nodes should be from the 'before' snapshot
|
|
kdl_out = write_kdl(state.nodes)
|
|
self.assertIn("8", kdl_out)
|
|
|
|
def test_apply_undo_empty_returns_none(self):
|
|
state = AppState()
|
|
result = state.apply_undo()
|
|
self.assertIsNone(result)
|
|
|
|
def test_apply_redo_empty_returns_none(self):
|
|
state = AppState()
|
|
result = state.apply_redo()
|
|
self.assertIsNone(result)
|
|
|
|
def test_undo_sets_dirty(self):
|
|
state = self._make_state_with_nodes("gaps 8\n")
|
|
state.push_undo("x", "gaps 16\n", "gaps 24\n")
|
|
state.apply_undo()
|
|
self.assertTrue(state.is_dirty)
|
|
|
|
def test_redo_after_undo(self):
|
|
state = self._make_state_with_nodes("gaps 8\n")
|
|
state.push_undo("x", "gaps 8\n", "gaps 16\n")
|
|
state.apply_undo()
|
|
self.assertTrue(state.undo.can_redo())
|
|
entry = state.apply_redo()
|
|
self.assertIsNotNone(entry)
|
|
kdl_out = write_kdl(state.nodes)
|
|
self.assertIn("16", kdl_out)
|
|
|
|
|
|
class TestCommitSave(unittest.TestCase):
|
|
def test_commit_save_clears_undo_and_dirty(self):
|
|
state = AppState()
|
|
state.push_undo("x", "a", "b")
|
|
state.mark_dirty()
|
|
state.commit_save("new kdl\n")
|
|
self.assertEqual(state.saved_kdl, "new kdl\n")
|
|
self.assertFalse(state.is_dirty)
|
|
self.assertFalse(state.undo.can_undo())
|
|
|
|
|
|
class TestDiscard(unittest.TestCase):
|
|
def test_discard_restores_saved_kdl(self):
|
|
state = AppState()
|
|
state._saved_kdl = "gaps 8\n"
|
|
state.nodes = parse_kdl("gaps 16\n")
|
|
state.mark_dirty()
|
|
state.push_undo("x", "gaps 8\n", "gaps 16\n")
|
|
|
|
state.discard()
|
|
|
|
self.assertFalse(state.is_dirty)
|
|
self.assertFalse(state.undo.can_undo())
|
|
kdl_out = write_kdl(state.nodes)
|
|
self.assertIn("8", kdl_out)
|
|
|
|
def test_discard_empty_saved_kdl(self):
|
|
state = AppState()
|
|
state._saved_kdl = ""
|
|
state.discard()
|
|
self.assertEqual(state.nodes, [])
|
|
|
|
|
|
class TestWriteCurrentKdl(unittest.TestCase):
|
|
def test_write_current_kdl(self):
|
|
state = AppState()
|
|
state.nodes = [KdlNode("prefer-no-csd")]
|
|
out = state.write_current_kdl()
|
|
self.assertIn("prefer-no-csd", out)
|
|
|
|
def test_write_raw_string(self):
|
|
# A string containing a double-quote forces the hash-delimited raw form
|
|
node = KdlNode("env", args=[KdlRawString('has "double" quotes')])
|
|
out = write_kdl([node])
|
|
self.assertIn('r#"', out)
|
|
|
|
|
|
class TestLoad(unittest.TestCase):
|
|
def test_load_detects_runtime(self):
|
|
state = AppState()
|
|
# state.py does: from nirimod import niri_ipc
|
|
# We patch at the canonical module location so all references see the mock.
|
|
with (
|
|
patch("nirimod.niri_ipc.is_niri_running", return_value=True),
|
|
patch("nirimod.niri_ipc.has_touchpad", return_value=True),
|
|
patch("nirimod.kdl_parser.NIRI_CONFIG") as mock_cfg,
|
|
):
|
|
mock_cfg.exists.return_value = False
|
|
state.load()
|
|
|
|
self.assertTrue(state.niri_running)
|
|
self.assertTrue(state.has_touchpad)
|
|
self.assertFalse(state.is_dirty)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|