i3pystatus/tests/test_buds.py
2025-02-06 19:44:25 +01:00

544 lines
20 KiB
Python

import json
import os.path
import unittest
from copy import deepcopy
from unittest.mock import patch
from i3pystatus.core.color import ColorRangeModule
from i3pystatus.buds import Buds, BudsEqualizer, BudsPlacementStatus
class TestBuds(unittest.TestCase):
def setUp(self):
self.buds = Buds()
with open(os.path.join(os.path.dirname(__file__), 'test_buds.json'), 'rb') as file:
self.payload = json.load(file)
@patch('i3pystatus.buds.run_through_shell')
def test_run_device_connected(self, mock_run):
# Setup: Use json.dumps as we expect JSON
payload = self.payload.get('connected_payload')
mock_run.return_value.out = json.dumps(payload)
# Action: Call run() and save return for comparison
buds_run_return = self.buds.run()
# Verify: Assert called with right params
mock_run.assert_called_with(f"{self.buds.earbuds_binary} status -o json -q")
expected_output = {
"full_text": "Buds2 LW53 48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Assert correct output
self.assertEqual(expected_output, self.buds.output)
# Verify: run() return is equal to payload
self.assertDictEqual(payload.get('payload'), buds_run_return)
@patch('i3pystatus.buds.run_through_shell')
def test_run_device_disconnected(self, mock_run):
# Setup: Use json.dumps as we expect JSON
mock_run.return_value.out = json.dumps(self.payload.get('disconnected_payload'))
# Action: Call run() and save return for comparison
buds_run_return = self.buds.run()
# Verify: Assert called with right params
mock_run.assert_called_with(f"{self.buds.earbuds_binary} status -o json -q")
expected_output = {
"full_text": "Disconnected",
"color": self.buds.disconnected_color
}
# Verify: Assert correct output
self.assertEqual(expected_output, self.buds.output)
# Verify: run() return should be none
self.assertIsNone(buds_run_return)
@patch('i3pystatus.buds.run_through_shell')
def test_toggle_amb(self, mock_run):
# Setup: AMB is initially disabled
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['ambient_sound_enabled'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Toggle AMB
self.buds.toggle_amb()
# Verify: The correct command is sent to enable AMB
mock_run.assert_called_with(f"{self.buds.earbuds_binary} set ambientsound 1")
# Setup: Change the payload again to update the AMB status
modified_payload['payload']['ambient_sound_enabled'] = True
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run again to update output
self.buds.run()
# Verify: The output correctly displays AMB is enabled
expected_output = {
"full_text": "Buds2 LW53 48RW AMB",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
# Action: Toggle AMB again
self.buds.toggle_amb()
# Verify: The correct command is sent to disable AMB this time
mock_run.assert_called_with(f"{self.buds.earbuds_binary} set ambientsound 0")
# Setup: Change the payload one last time to update the AMB status
modified_payload['payload']['ambient_sound_enabled'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run again to update output
self.buds.run()
# Verify: The output correctly displays AMB is disabled
expected_output = {
"full_text": "Buds2 LW53 48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_toggle_anc(self, mock_run):
# Setup: ANC is initially disabled
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['noise_reduction'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Toggle ANC
self.buds.toggle_anc()
# Verify: The correct command is sent to enable ANC
mock_run.assert_called_with(f"{self.buds.earbuds_binary} set anc true")
# Setup: Change the payload again to update the ANC status
modified_payload['payload']['noise_reduction'] = True
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run again to update output
self.buds.run()
# Verify: The output correctly displays ANC is enabled
expected_output = {
"full_text": "Buds2 LW53 48RW ANC",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
# Action: Toggle ANC again
self.buds.toggle_anc()
# Verify: The correct command is sent to disable ANC this time
mock_run.assert_called_with(f"{self.buds.earbuds_binary} set anc false")
# Setup: Change the payload one last time to update the ANC status
modified_payload['payload']['noise_reduction'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run again to update output
self.buds.run()
# Verify: The output correctly displays ANC is disabled
expected_output = {
"full_text": "Buds2 LW53 48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_combined_battery(self, mock_run):
# Setup: Equal left and right battery value
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['batt_left'] = modified_payload['payload']['batt_right']
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() to update the output
self.buds.run()
expected_output = {
"full_text": "Buds2 LW48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
# Setup: Different left and right battery value
mock_run.return_value.out = json.dumps(self.payload.get('connected_payload'))
# Action: Call run() again to update the output
self.buds.run()
expected_output = {
"full_text": "Buds2 LW53 48RW",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_combined_battery_drift(self, mock_run):
# Setup: Different battery level, should show smaller
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['batt_left'] = modified_payload['payload']['batt_right']
modified_payload['payload']['batt_left'] -= self.buds.battery_drift_threshold
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() to update the output
self.buds.run()
expected_level = min(modified_payload['payload']['batt_left'], modified_payload['payload']['batt_right'])
expected_output = {
# Verify: The level should be the smallest one
"full_text":
f"Buds2 LW{expected_level}RW",
"color": self.buds.get_gradient(
expected_level,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
# Setup: One battery is at level 0, should show the other
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['batt_left'] = 0
modified_payload['payload']['batt_right'] = 0 + self.buds.battery_drift_threshold
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_level = max(modified_payload['payload']['batt_left'], modified_payload['payload']['batt_right'])
expected_output = {
# Verify: The level should be the biggest one
"full_text":
f"Buds2 LW{expected_level}RW",
"color": self.buds.get_gradient(
expected_level,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_combined_battery_drift_case(self, mock_run):
# Setup: Change status of one buds to be on the case
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['placement_left'] = BudsPlacementStatus.case
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() to update the output
self.buds.run()
expected_output = {
"full_text": f"Buds2 LC53 48RW 88C",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: The output correctly displays combined battery status
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_connect(self, mock_run):
# Action: Call connect
self.buds.connect()
# Verify: The correct command is sent to connect
mock_run.assert_called_with(f"{self.buds.earbuds_binary} connect")
@patch('i3pystatus.buds.run_through_shell')
def test_disconnect(self, mock_run):
# Action: Call disconnect
self.buds.disconnect()
# Verify: The correct command is sent to disconnect
mock_run.assert_called_with(f"{self.buds.earbuds_binary} disconnect")
@patch('i3pystatus.buds.run_through_shell')
def test_restart_daemin(self, mock_run):
# Action: Call restart_daemon
self.buds.restart_daemon()
# Verify: The correct command is sent to restart the daemon
mock_run.assert_called_with(f"{self.buds.earbuds_binary} -kd")
def run_placement_helper(self, mock_run, placement_left, placement_right, case_battery, expected_display):
modified_payload = deepcopy(self.payload.get('connected_payload'))
modified_payload['payload']['placement_left'] = placement_left
modified_payload['payload']['placement_right'] = placement_right
if case_battery is not None:
modified_payload['payload']['batt_case'] = case_battery
mock_run.return_value.out = json.dumps(modified_payload)
self.buds.run()
expected_output = {
"full_text": expected_display,
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_placement_wearing(self, mock_run):
self.run_placement_helper(
mock_run,
BudsPlacementStatus.wearing.value,
BudsPlacementStatus.wearing.value,
None,
"Buds2 LW53 48RW"
)
@patch('i3pystatus.buds.run_through_shell')
def test_placement_idle(self, mock_run):
self.run_placement_helper(
mock_run,
BudsPlacementStatus.idle.value,
BudsPlacementStatus.idle.value,
None,
"Buds2 LI53 48RI"
)
@patch('i3pystatus.buds.run_through_shell')
def test_placement_case_with_battery(self, mock_run):
# Verify: Case battery is returned if a bud is on the case
self.run_placement_helper(
mock_run,
BudsPlacementStatus.case.value,
BudsPlacementStatus.case.value,
88,
"Buds2 LC53 48RC 88C"
)
@patch('i3pystatus.buds.run_through_shell')
def test_battery_level_dynamic_color(self, mock_run):
# Setup: Build the colors array independently of our class
colors = ColorRangeModule.get_hex_color_range(
self.buds.end_color,
self.buds.start_color,
self.buds.battery_limit
)
modified_payload = deepcopy(self.payload.get('connected_payload'))
for battery_level in range(0, self.buds.battery_limit + 1):
# Setup: Make both levels equal
modified_payload['payload']['batt_left'] = battery_level
modified_payload['payload']['batt_right'] = battery_level
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_output = {
"full_text": f"Buds2 LW{battery_level}RW",
"color": self.buds.get_gradient(
battery_level,
colors,
self.buds.battery_limit
)
}
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_set_equalizer_direct(self, mock_run):
for eq_setting in BudsEqualizer:
with self.subTest(msg=f"Failed testing equalizer {eq_setting.name}", eq_setting=eq_setting):
# Setup: Create a copy of the payload
modified_payload = deepcopy(self.payload.get('connected_payload'))
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call the set function with each equalizer setting
self.buds.equalizer_set(eq_setting)
expected_command = f"{self.buds.earbuds_binary} set equalizer {eq_setting.name}"
# Verify: Correct equalizer command is used
mock_run.assert_called_with(expected_command)
# Setup: Modify payload to verify output
modified_payload['payload']['equalizer_type'] = eq_setting.value
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_equalizer = f" {eq_setting.name.capitalize()}" if eq_setting.name != "off" else ""
expected_output = {
"full_text": f"Buds2 LW53 48RW{expected_equalizer}",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Output was updated with equalizer
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_increment_equalizer(self, mock_run):
# Setup: Create a copy of the payload
modified_payload = deepcopy(self.payload.get('connected_payload'))
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call the set to increment by one the equalizer setting
self.buds.equalizer_set(+1)
# Verify: Correct equalizer command is used
expected_command = f"{self.buds.earbuds_binary} set equalizer {BudsEqualizer.bass.name}"
mock_run.assert_called_with(expected_command)
# Setup: Modify payload to verify output
modified_payload['payload']['equalizer_type'] = BudsEqualizer.bass.value
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_equalizer = f" {BudsEqualizer.bass.name.capitalize()}"
expected_output = {
"full_text": f"Buds2 LW53 48RW{expected_equalizer}",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Output was updated with equalizer
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_decrement_equalizer_from_off(self, mock_run):
# Setup: Create a copy of the payload
modified_payload = deepcopy(self.payload.get('connected_payload'))
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call the set to decrement by one the equalizer setting
self.buds.equalizer_set(-1)
# Verify: Correct equalizer command is used
expected_command = f"{self.buds.earbuds_binary} set equalizer {BudsEqualizer.treble.name}"
mock_run.assert_called_with(expected_command)
# Setup: Modify payload to verify output
modified_payload['payload']['equalizer_type'] = BudsEqualizer.treble.value
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
expected_equalizer = f" {BudsEqualizer.treble.name.capitalize()}"
expected_output = {
"full_text": f"Buds2 LW53 48RW{expected_equalizer}",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Output was updated with equalizer
self.assertEqual(expected_output, self.buds.output)
def run_touchpad_set(self, mock_run, setting_value):
# Setup: Create a copy of the payload
modified_payload = deepcopy(self.payload.get('connected_payload'))
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call the set with the appropriate setting
self.buds.touchpad_set(f'{setting_value}')
# Verify: Correct command to disable the touchpad is called
expected_command = f"{self.buds.earbuds_binary} set touchpad {setting_value}"
mock_run.assert_called_with(expected_command)
# Setup: Modify the payload if we are disabling the touchpad
if setting_value == 'false':
modified_payload['payload']['tab_lock_status']['touch_an_hold_on'] = False
modified_payload['payload']['tab_lock_status']['tap_on'] = False
mock_run.return_value.out = json.dumps(modified_payload)
# Action: Call run() again to update the output
self.buds.run()
# Setup:
expected_output = {
"full_text": f"Buds2 LW53 48RW{' TL' if setting_value == 'false' else ''}",
"color": self.buds.get_gradient(
48,
self.buds.colors,
self.buds.battery_limit
)
}
# Verify: Output was updated with equalizer
self.assertEqual(expected_output, self.buds.output)
@patch('i3pystatus.buds.run_through_shell')
def test_touchpad_disable(self, mock_run):
self.run_touchpad_set(mock_run, "false")
@patch('i3pystatus.buds.run_through_shell')
def test_touchpad_enable(self, mock_run):
self.run_touchpad_set(mock_run, "true")