aboutsummaryrefslogtreecommitdiff
path: root/tests/waybar-airplane/test_waybar_airplane.py
blob: b3b9c067bf61f24e7d1652be269ddc12eabf021d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""Tests for dotfiles/hyprland/.local/bin/waybar-airplane.

The script emits one JSON line for waybar's custom/airplane module, derived
from the airplane-mode state file that the airplane-mode toggle maintains at
$XDG_RUNTIME_DIR/airplane-state. The file holds key=value lines; the only
key the indicator reads is `mode` (on/off). Tests point XDG_RUNTIME_DIR at a
temp dir and assert on the emitted JSON. No mocking: the real script runs
against a real state file.

Run from repo root:
    python3 -m unittest tests.waybar-airplane.test_waybar_airplane
"""

import json
import os
import shutil
import subprocess
import tempfile
import unittest


REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
SCRIPT = os.path.join(REPO_ROOT, "dotfiles/hyprland/.local/bin/waybar-airplane")


class WaybarAirplaneHarness(unittest.TestCase):

    def setUp(self):
        self.tmp = tempfile.mkdtemp(prefix="waybar-airplane-test-")
        self.state_file = os.path.join(self.tmp, "airplane-state")
        # Fake power-supply dir with a battery → the script treats the host as
        # a laptop. Desktop tests point this at a battery-less dir.
        self.ps_dir = os.path.join(self.tmp, "power_supply")
        os.makedirs(os.path.join(self.ps_dir, "BAT0"))

    def tearDown(self):
        shutil.rmtree(self.tmp, ignore_errors=True)

    def set_state(self, contents):
        with open(self.state_file, "w") as f:
            f.write(contents)

    def run_script(self):
        env = os.environ.copy()
        env["XDG_RUNTIME_DIR"] = self.tmp
        env["AIRPLANE_POWER_SUPPLY_DIR"] = self.ps_dir
        return subprocess.run(
            [SCRIPT], env=env, capture_output=True, text=True, timeout=10,
        )

    def emitted_json(self):
        result = self.run_script()
        self.assertEqual(result.returncode, 0, msg=result.stderr)
        return json.loads(result.stdout)


# -----------------------------------------------------------------------------
# Normal cases
# -----------------------------------------------------------------------------

class TestWaybarAirplaneNormal(WaybarAirplaneHarness):

    def test_mode_on_emits_active_class_and_plane_icon(self):
        # State is carried by class/color, not a slashed glyph: the same clear
        # plane shows in both states (gold when active, gray when inactive).
        self.set_state("mode=on\nwifi=enabled\n")
        data = self.emitted_json()
        self.assertEqual(data["class"], "active")
        self.assertIn("", data["text"])  #  plane
        self.assertIn("on", data["tooltip"].lower())

    def test_mode_off_emits_inactive_class_and_plane_icon(self):
        self.set_state("mode=off\n")
        data = self.emitted_json()
        self.assertEqual(data["class"], "inactive")
        self.assertIn("", data["text"])  #  plane
        self.assertIn("off", data["tooltip"].lower())


# -----------------------------------------------------------------------------
# Boundary cases
# -----------------------------------------------------------------------------

class TestWaybarAirplaneBoundary(WaybarAirplaneHarness):

    def test_missing_state_file_defaults_to_inactive(self):
        # No state file → airplane mode is not engaged (radios on = safe default).
        data = self.emitted_json()
        self.assertEqual(data["class"], "inactive")

    def test_mode_on_with_other_keys_present(self):
        # The toggle writes several keys; the indicator only cares about mode.
        self.set_state(
            "mode=on\nwifi=enabled\nepp=balance_performance\n"
            "brightness=96000\nstopped_system=sshd.service tailscaled.service\n"
        )
        data = self.emitted_json()
        self.assertEqual(data["class"], "active")

    def test_unknown_mode_value_treated_as_inactive(self):
        # Anything that isn't "on" reads as off (fail-safe: radios shown as on).
        self.set_state("mode=garbage\n")
        data = self.emitted_json()
        self.assertEqual(data["class"], "inactive")

    def test_empty_state_file_defaults_to_inactive(self):
        self.set_state("")
        data = self.emitted_json()
        self.assertEqual(data["class"], "inactive")

    def test_output_is_a_single_json_object(self):
        self.set_state("mode=on\n")
        result = self.run_script()
        lines = [ln for ln in result.stdout.splitlines() if ln.strip()]
        self.assertEqual(len(lines), 1, msg=f"expected one line, got {lines!r}")


# -----------------------------------------------------------------------------
# Laptop gating — the module only shows on machines with a battery
# -----------------------------------------------------------------------------

class TestWaybarAirplaneLaptopGating(WaybarAirplaneHarness):

    def run_desktop(self):
        # Point the power-supply dir at a battery-less location (a desktop).
        env = os.environ.copy()
        env["XDG_RUNTIME_DIR"] = self.tmp
        empty = os.path.join(self.tmp, "power_supply_desktop")
        os.makedirs(empty, exist_ok=True)  # AC adapter only, no BAT*
        os.makedirs(os.path.join(empty, "ADP1"), exist_ok=True)
        env["AIRPLANE_POWER_SUPPLY_DIR"] = empty
        return subprocess.run(
            [SCRIPT], env=env, capture_output=True, text=True, timeout=10,
        )

    def test_desktop_emits_nothing(self):
        # No battery → module hidden → script prints no output.
        self.set_state("mode=off\n")
        result = self.run_desktop()
        self.assertEqual(result.returncode, 0, msg=result.stderr)
        self.assertEqual(result.stdout.strip(), "")

    def test_desktop_emits_nothing_even_when_engaged(self):
        self.set_state("mode=on\n")
        result = self.run_desktop()
        self.assertEqual(result.stdout.strip(), "")

    def test_laptop_with_battery_emits_module(self):
        # Sanity: the battery-present harness (default) does emit JSON.
        self.set_state("mode=off\n")
        data = self.emitted_json()
        self.assertEqual(data["class"], "inactive")


if __name__ == "__main__":
    unittest.main()