2023-12-18 10:17:08 -06:00
|
|
|
"""Test the Textual GUI.
|
|
|
|
|
2024-01-02 15:15:55 -06:00
|
|
|
SPDX-FileCopyrightText: © 2023 Brian S. Stephan <bss@incorporeal.org>
|
2024-03-07 09:20:34 -06:00
|
|
|
SPDX-License-Identifier: GPL-3.0-or-later
|
2023-12-18 10:17:08 -06:00
|
|
|
"""
|
2023-06-27 08:55:30 -05:00
|
|
|
import os
|
|
|
|
import sys
|
2023-07-12 00:08:55 -05:00
|
|
|
import unittest.mock as mock
|
2023-06-27 08:55:30 -05:00
|
|
|
|
|
|
|
import pytest
|
2023-07-01 13:56:52 -05:00
|
|
|
from decorator import decorator
|
2023-06-27 08:55:30 -05:00
|
|
|
from textual.widgets import Tree
|
|
|
|
|
2023-07-12 00:08:55 -05:00
|
|
|
from gp2040ce_bintools import get_config_pb2
|
2023-06-27 08:55:30 -05:00
|
|
|
from gp2040ce_bintools.gui import ConfigEditor
|
2023-07-12 00:08:55 -05:00
|
|
|
from gp2040ce_bintools.storage import ConfigReadError, get_config, get_config_from_file
|
2023-06-27 08:55:30 -05:00
|
|
|
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
2023-07-01 13:56:52 -05:00
|
|
|
|
|
|
|
|
|
|
|
@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']
|
2023-06-27 08:55:30 -05:00
|
|
|
|
|
|
|
|
2023-07-12 00:08:55 -05:00
|
|
|
@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
|
|
|
|
|
|
|
|
|
2023-06-27 08:55:30 -05:00
|
|
|
@pytest.mark.asyncio
|
2023-07-01 13:56:52 -05:00
|
|
|
@with_pb2s
|
2023-06-27 08:55:30 -05:00
|
|
|
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:
|
2023-11-06 23:55:01 -06:00
|
|
|
check_node = pilot.app.query_one(Tree).root.children[3]
|
|
|
|
assert "boardVersion = 'v0.7.5'" in check_node.label
|
2023-07-01 13:00:48 -05:00
|
|
|
parent_config, field_descriptor, field_value = check_node.data
|
2023-06-27 08:55:30 -05:00
|
|
|
assert parent_config == pilot.app.config
|
|
|
|
assert field_descriptor == pilot.app.config.DESCRIPTOR.fields_by_name['boardVersion']
|
2023-11-06 23:55:01 -06:00
|
|
|
assert field_value == 'v0.7.5'
|
2023-07-01 13:56:52 -05:00
|
|
|
app.exit()
|
2023-06-27 08:55:30 -05:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
2023-07-01 13:56:52 -05:00
|
|
|
@with_pb2s
|
2023-06-27 08:55:30 -05:00
|
|
|
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)
|
2023-11-06 23:55:01 -06:00
|
|
|
display_node = tree.root.children[5]
|
2024-01-03 12:19:54 -06:00
|
|
|
invert_node = display_node.children[10]
|
2023-06-27 08:55:30 -05:00
|
|
|
|
|
|
|
assert 'False' in invert_node.label
|
|
|
|
app._modify_node(invert_node)
|
|
|
|
assert 'True' in invert_node.label
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
2023-07-01 13:56:52 -05:00
|
|
|
@with_pb2s
|
2023-06-27 08:55:30 -05:00
|
|
|
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)
|
2023-11-06 23:55:01 -06:00
|
|
|
display_node = tree.root.children[5]
|
2024-01-03 12:19:54 -06:00
|
|
|
i2cspeed_node = display_node.children[4]
|
|
|
|
assert pilot.app.config.displayOptions.deprecatedI2cSpeed == 400000
|
2023-06-27 08:55:30 -05:00
|
|
|
|
|
|
|
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()
|
2024-02-17 23:05:51 -06:00
|
|
|
await pilot.click('Button#confirm-button')
|
2024-01-03 12:19:54 -06:00
|
|
|
assert pilot.app.config.displayOptions.deprecatedI2cSpeed == 5
|
2023-06-27 08:55:30 -05:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
2023-07-01 13:56:52 -05:00
|
|
|
@with_pb2s
|
2023-06-27 08:55:30 -05:00
|
|
|
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)
|
2023-11-06 23:55:01 -06:00
|
|
|
gamepad_node = tree.root.children[7]
|
2023-06-27 08:55:30 -05:00
|
|
|
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()
|
2024-02-17 23:05:51 -06:00
|
|
|
await pilot.click('Button#confirm-button')
|
2023-06-27 08:55:30 -05:00
|
|
|
assert pilot.app.config.gamepadOptions.dpadMode == 1
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
2023-07-01 13:56:52 -05:00
|
|
|
@with_pb2s
|
2023-06-27 08:55:30 -05:00
|
|
|
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)
|
2023-11-06 23:55:01 -06:00
|
|
|
version_node = tree.root.children[3]
|
|
|
|
assert pilot.app.config.boardVersion == 'v0.7.5'
|
2023-06-27 08:55:30 -05:00
|
|
|
|
|
|
|
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()
|
2024-02-17 23:05:51 -06:00
|
|
|
await pilot.click('Button#confirm-button')
|
2023-06-27 08:55:30 -05:00
|
|
|
assert pilot.app.config.boardVersion == 'v0.7.-hi'
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
2023-07-01 13:56:52 -05:00
|
|
|
@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)
|
2023-11-06 23:55:01 -06:00
|
|
|
profile_node = tree.root.children[13]
|
2023-07-01 13:56:52 -05:00
|
|
|
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()
|
2024-02-17 23:05:51 -06:00
|
|
|
await pilot.click('Button#confirm-button')
|
2023-07-01 13:56:52 -05:00
|
|
|
|
2024-01-03 12:19:54 -06:00
|
|
|
assert pilot.app.config.profileOptions.deprecatedAlternativePinMappings[0].pinButtonB4 == 5
|
2023-07-01 13:56:52 -05:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
@with_pb2s
|
2023-06-27 08:55:30 -05:00
|
|
|
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:
|
2023-11-06 23:55:01 -06:00
|
|
|
pilot.app.config.boardVersion = 'v0.7.5-bss-wuz-here'
|
2023-06-27 08:55:30 -05:00
|
|
|
await pilot.press('s')
|
|
|
|
|
|
|
|
config = get_config_from_file(new_filename)
|
2023-11-06 23:55:01 -06:00
|
|
|
assert config.boardVersion == 'v0.7.5-bss-wuz-here'
|