gp2040ce-binary-tools/tests/test_gui.py

215 lines
8.6 KiB
Python
Raw Normal View History

"""Test the Textual GUI."""
import os
import sys
import unittest.mock as mock
import pytest
from decorator import decorator
from textual.widgets import Tree
from gp2040ce_bintools import get_config_pb2
from gp2040ce_bintools.gui import ConfigEditor
from gp2040ce_bintools.storage import ConfigReadError, get_config, get_config_from_file
HERE = os.path.dirname(os.path.abspath(__file__))
@decorator
async def with_pb2s(test, *args, **kwargs):
"""Wrap a test with precompiled pb2 files on the path."""
proto_path = os.path.join(HERE, 'test-files', 'pb2-files')
sys.path.append(proto_path)
await test(*args, **kwargs)
sys.path.pop()
del sys.modules['config_pb2']
@pytest.mark.asyncio
@with_pb2s
async def test_load_configs():
"""Test a variety of ways the editor may get initialized."""
test_config_filename = os.path.join(HERE, 'test-files/test-config.bin')
empty_config = get_config_pb2().Config()
with open(test_config_filename, 'rb') as file_:
test_config_binary = file_.read()
test_config = get_config(test_config_binary)
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-config.bin'))
assert app.config == test_config
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-config.binooooooo'), create_new=True)
assert app.config == empty_config
with pytest.raises(FileNotFoundError):
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-config.binooooooo'))
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-firmware.bin'), create_new=True)
assert app.config == empty_config
with pytest.raises(ConfigReadError):
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-firmware.bin'))
with mock.patch('gp2040ce_bintools.gui.get_bootsel_endpoints', return_value=(mock.MagicMock(), mock.MagicMock())):
with mock.patch('gp2040ce_bintools.gui.read', return_value=b'\x00'):
with pytest.raises(ConfigReadError):
app = ConfigEditor(usb=True)
with mock.patch('gp2040ce_bintools.gui.get_bootsel_endpoints', return_value=(mock.MagicMock(), mock.MagicMock())):
with mock.patch('gp2040ce_bintools.gui.read', return_value=b'\x00'):
app = ConfigEditor(usb=True, create_new=True)
assert app.config == empty_config
with mock.patch('gp2040ce_bintools.gui.get_bootsel_endpoints', return_value=(mock.MagicMock(), mock.MagicMock())):
with mock.patch('gp2040ce_bintools.gui.read', return_value=test_config_binary):
app = ConfigEditor(usb=True)
assert app.config == test_config
@pytest.mark.asyncio
@with_pb2s
async def test_simple_tree_building():
"""Test some basics of the config tree being built."""
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-config.bin'))
async with app.run_test() as pilot:
check_node = pilot.app.query_one(Tree).root.children[3]
assert "boardVersion = 'v0.7.5'" in check_node.label
parent_config, field_descriptor, field_value = check_node.data
assert parent_config == pilot.app.config
assert field_descriptor == pilot.app.config.DESCRIPTOR.fields_by_name['boardVersion']
assert field_value == 'v0.7.5'
app.exit()
@pytest.mark.asyncio
@with_pb2s
async def test_simple_toggle():
"""Test that we can navigate a bit and toggle a bool."""
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-config.bin'))
async with app.run_test() as pilot:
tree = pilot.app.query_one(Tree)
display_node = tree.root.children[5]
invert_node = display_node.children[11]
assert 'False' in invert_node.label
app._modify_node(invert_node)
assert 'True' in invert_node.label
@pytest.mark.asyncio
@with_pb2s
async def test_simple_edit_via_input_field():
"""Test that we can change an int via UI and see it reflected in the config."""
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-config.bin'))
async with app.run_test() as pilot:
tree = pilot.app.query_one(Tree)
display_node = tree.root.children[5]
i2cspeed_node = display_node.children[10]
assert pilot.app.config.displayOptions.i2cSpeed == 400000
tree.root.expand_all()
await pilot.wait_for_scheduled_animations()
tree.select_node(i2cspeed_node)
tree.action_select_cursor()
await pilot.wait_for_scheduled_animations()
await pilot.click('Input#field-input')
await pilot.wait_for_scheduled_animations()
await pilot.press('backspace', 'backspace', 'backspace', 'backspace', 'backspace', 'backspace', '5')
await pilot.wait_for_scheduled_animations()
await pilot.click('Button#save-button')
assert pilot.app.config.displayOptions.i2cSpeed == 5
@pytest.mark.asyncio
@with_pb2s
async def test_simple_edit_via_input_field_enum():
"""Test that we can change an enum via the UI and see it reflected in the config."""
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-config.bin'))
async with app.run_test() as pilot:
tree = pilot.app.query_one(Tree)
gamepad_node = tree.root.children[7]
dpadmode_node = gamepad_node.children[0]
assert pilot.app.config.gamepadOptions.dpadMode == 0
tree.root.expand_all()
await pilot.wait_for_scheduled_animations()
tree.select_node(dpadmode_node)
tree.action_select_cursor()
await pilot.wait_for_scheduled_animations()
await pilot.click('Select#field-input')
await pilot.wait_for_scheduled_animations()
await pilot.press('down', 'down', 'enter')
await pilot.wait_for_scheduled_animations()
await pilot.click('Button#save-button')
assert pilot.app.config.gamepadOptions.dpadMode == 1
@pytest.mark.asyncio
@with_pb2s
async def test_simple_edit_via_input_field_string():
"""Test that we can change a string via the UI and see it reflected in the config."""
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-config.bin'))
async with app.run_test() as pilot:
tree = pilot.app.query_one(Tree)
version_node = tree.root.children[3]
assert pilot.app.config.boardVersion == 'v0.7.5'
tree.select_node(version_node)
tree.action_select_cursor()
await pilot.wait_for_scheduled_animations()
await pilot.click('Input#field-input')
await pilot.wait_for_scheduled_animations()
await pilot.press('backspace', '-', 'h', 'i')
await pilot.wait_for_scheduled_animations()
await pilot.click('Button#save-button')
assert pilot.app.config.boardVersion == 'v0.7.-hi'
@pytest.mark.asyncio
@with_pb2s
async def test_add_node_to_repeated():
"""Test that we can navigate to an empty repeated and add a node."""
app = ConfigEditor(config_filename=os.path.join(HERE, 'test-files/test-config.bin'))
async with app.run_test() as pilot:
tree = pilot.app.query_one(Tree)
profile_node = tree.root.children[13]
altpinmappings_node = profile_node.children[0]
tree.root.expand_all()
await pilot.wait_for_scheduled_animations()
tree.select_node(altpinmappings_node)
await pilot.press('a')
newpinmappings_node = altpinmappings_node.children[0]
newpinmappings_node.expand()
await pilot.wait_for_scheduled_animations()
tree.select_node(newpinmappings_node)
b4_node = newpinmappings_node.children[3]
tree.select_node(b4_node)
tree.action_select_cursor()
await pilot.wait_for_scheduled_animations()
await pilot.click('Input#field-input')
await pilot.wait_for_scheduled_animations()
await pilot.press('backspace', 'backspace', 'backspace', 'backspace', 'backspace', 'backspace', '5')
await pilot.wait_for_scheduled_animations()
await pilot.click('Button#save-button')
assert pilot.app.config.profileOptions.alternativePinMappings[0].pinButtonB4 == 5
@pytest.mark.asyncio
@with_pb2s
async def test_save(config_binary, tmp_path):
"""Test that the tree builds and things are kind of where they should be."""
new_filename = os.path.join(tmp_path, 'config-copy.bin')
with open(new_filename, 'wb') as file:
file.write(config_binary)
app = ConfigEditor(config_filename=new_filename)
async with app.run_test() as pilot:
pilot.app.config.boardVersion = 'v0.7.5-bss-wuz-here'
await pilot.press('s')
config = get_config_from_file(new_filename)
assert config.boardVersion == 'v0.7.5-bss-wuz-here'