983 lines
33 KiB
Python
983 lines
33 KiB
Python
# Unit tests for preferences_grid_base.py.
|
|
#
|
|
# Copyright 2026 Igalia, S.L.
|
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
|
#
|
|
# This library is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU Lesser General Public
|
|
# License as published by the Free Software Foundation; either
|
|
# version 2.1 of the License, or (at your option) any later version.
|
|
#
|
|
# This library is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this library; if not, write to the
|
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
|
# Boston MA 02110-1301 USA.
|
|
|
|
# pylint: disable=wrong-import-position
|
|
# pylint: disable=protected-access
|
|
# pylint: disable=import-outside-toplevel
|
|
|
|
"""Unit tests for preferences_grid_base.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import gi
|
|
import pytest
|
|
|
|
gi.require_version("Gtk", "3.0")
|
|
from gi.repository import Gtk
|
|
|
|
from cthulhu.preferences_grid_base import (
|
|
AutoPreferencesGrid,
|
|
BooleanPreferenceControl,
|
|
CategoryListBoxRow,
|
|
CommandListBoxRow,
|
|
ControlType,
|
|
EnumPreferenceControl,
|
|
FloatRangePreferenceControl,
|
|
FocusManagedListBox,
|
|
IntRangePreferenceControl,
|
|
PreferencesGridBase,
|
|
RadioButtonWithActions,
|
|
SelectionPreferenceControl,
|
|
StackedPreferencesHelper,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPreferenceControlDataclasses:
|
|
"""Test preference control dataclass creation and properties."""
|
|
|
|
def test_boolean_preference_control_creation(self) -> None:
|
|
"""Test BooleanPreferenceControl can be created with required fields."""
|
|
|
|
getter_called: list[bool] = [False]
|
|
setter_value: list[bool | None] = [None]
|
|
|
|
def getter() -> bool:
|
|
getter_called[0] = True
|
|
return True
|
|
|
|
def setter(value: bool) -> bool:
|
|
setter_value[0] = value
|
|
return True
|
|
|
|
control = BooleanPreferenceControl(
|
|
label="Test Label",
|
|
getter=getter,
|
|
setter=setter,
|
|
prefs_key="testKey",
|
|
)
|
|
|
|
assert control.label == "Test Label"
|
|
assert control.prefs_key == "testKey"
|
|
assert control.getter() is True
|
|
assert getter_called[0] is True
|
|
control.setter(False)
|
|
assert setter_value[0] is False
|
|
|
|
def test_boolean_preference_control_optional_fields(self) -> None:
|
|
"""Test BooleanPreferenceControl optional fields have correct defaults."""
|
|
|
|
control = BooleanPreferenceControl(label="Test", getter=lambda: True, setter=lambda x: True)
|
|
|
|
assert control.prefs_key is None
|
|
assert control.member_of is None
|
|
assert control.determine_sensitivity is None
|
|
assert control.apply_immediately is True
|
|
|
|
def test_int_range_preference_control_creation(self) -> None:
|
|
"""Test IntRangePreferenceControl can be created with all fields."""
|
|
|
|
control = IntRangePreferenceControl(
|
|
label="Volume",
|
|
minimum=0,
|
|
maximum=100,
|
|
getter=lambda: 50,
|
|
setter=lambda x: True,
|
|
prefs_key="volume",
|
|
)
|
|
|
|
assert control.label == "Volume"
|
|
assert control.minimum == 0
|
|
assert control.maximum == 100
|
|
assert control.getter() == 50
|
|
|
|
def test_float_range_preference_control_creation(self) -> None:
|
|
"""Test FloatRangePreferenceControl can be created with all fields."""
|
|
|
|
control = FloatRangePreferenceControl(
|
|
label="Rate",
|
|
minimum=0.0,
|
|
maximum=1.0,
|
|
getter=lambda: 0.5,
|
|
setter=lambda x: True,
|
|
)
|
|
|
|
assert control.label == "Rate"
|
|
assert control.minimum == 0.0
|
|
assert control.maximum == 1.0
|
|
assert control.getter() == 0.5
|
|
|
|
def test_enum_preference_control_creation(self) -> None:
|
|
"""Test EnumPreferenceControl can be created with options and values."""
|
|
|
|
control = EnumPreferenceControl(
|
|
label="Style",
|
|
options=["Option A", "Option B", "Option C"],
|
|
values=[1, 2, 3],
|
|
getter=lambda: 2,
|
|
setter=lambda x: True,
|
|
)
|
|
|
|
assert control.label == "Style"
|
|
assert len(control.options) == 3
|
|
assert control.values == [1, 2, 3]
|
|
assert control.getter() == 2
|
|
|
|
def test_selection_preference_control_creation(self) -> None:
|
|
"""Test SelectionPreferenceControl can be created with actions callback."""
|
|
|
|
def get_actions(_value: Any) -> list[tuple[str, str, Any]]:
|
|
return [("Edit", "edit-symbolic", lambda: None)]
|
|
|
|
control = SelectionPreferenceControl(
|
|
label="Profile",
|
|
options=["Default", "Custom"],
|
|
getter=lambda: "Default",
|
|
setter=lambda x: True,
|
|
get_actions_for_option=get_actions,
|
|
)
|
|
|
|
assert control.label == "Profile"
|
|
assert len(control.options) == 2
|
|
assert control.get_actions_for_option is not None
|
|
actions = control.get_actions_for_option("Default")
|
|
assert len(actions) == 1
|
|
assert actions[0][0] == "Edit"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestHelperClasses:
|
|
"""Test helper classes in preferences_grid_base."""
|
|
|
|
def test_category_list_box_row_stores_category(self) -> None:
|
|
"""Test CategoryListBoxRow stores and returns category identifier."""
|
|
|
|
row = CategoryListBoxRow("speech_settings")
|
|
assert row.category == "speech_settings"
|
|
|
|
def test_category_list_box_row_is_gtk_list_box_row(self) -> None:
|
|
"""Test CategoryListBoxRow inherits from Gtk.ListBoxRow."""
|
|
|
|
row = CategoryListBoxRow("test")
|
|
assert isinstance(row, Gtk.ListBoxRow)
|
|
|
|
def test_command_list_box_row_properties(self) -> None:
|
|
"""Test CommandListBoxRow property getters and setters."""
|
|
|
|
row = CommandListBoxRow()
|
|
assert row.command is None
|
|
assert row.vbox is None
|
|
assert row.binding_label is None
|
|
|
|
mock_command = object()
|
|
mock_vbox = Gtk.Box()
|
|
mock_label = Gtk.Label()
|
|
|
|
row.command = mock_command
|
|
row.vbox = mock_vbox
|
|
row.binding_label = mock_label
|
|
|
|
assert row.command is mock_command
|
|
assert row.vbox is mock_vbox
|
|
assert row.binding_label is mock_label
|
|
|
|
def test_radio_button_with_actions_stores_buttons(self) -> None:
|
|
"""Test RadioButtonWithActions stores action buttons list."""
|
|
|
|
radio = RadioButtonWithActions(label="Test Option")
|
|
assert not radio.action_buttons
|
|
|
|
button1 = Gtk.Button()
|
|
button2 = Gtk.Button()
|
|
radio.action_buttons = [button1, button2]
|
|
|
|
assert len(radio.action_buttons) == 2
|
|
assert radio.action_buttons[0] is button1
|
|
|
|
def test_radio_button_with_actions_is_gtk_radio_button(self) -> None:
|
|
"""Test RadioButtonWithActions inherits from Gtk.RadioButton."""
|
|
|
|
radio = RadioButtonWithActions(label="Test")
|
|
assert isinstance(radio, Gtk.RadioButton)
|
|
|
|
def test_stacked_preferences_helper_initial_state(self) -> None:
|
|
"""Test StackedPreferencesHelper initializes with None values."""
|
|
|
|
helper = StackedPreferencesHelper()
|
|
assert helper.stack is None
|
|
assert helper.categories_listbox is None
|
|
assert helper.detail_listbox is None
|
|
assert not helper.disable_widgets
|
|
assert helper.on_category_activated_callback is None
|
|
|
|
def test_stacked_preferences_helper_register_widgets(self) -> None:
|
|
"""Test StackedPreferencesHelper can register widgets to disable."""
|
|
|
|
helper = StackedPreferencesHelper()
|
|
button1 = Gtk.Button()
|
|
button2 = Gtk.Button()
|
|
|
|
helper.register_disable_widgets(button1, button2)
|
|
assert len(helper.disable_widgets) == 2
|
|
assert button1 in helper.disable_widgets
|
|
|
|
def test_stacked_preferences_helper_show_categories(self) -> None:
|
|
"""Test StackedPreferencesHelper show_categories enables registered widgets."""
|
|
|
|
helper = StackedPreferencesHelper()
|
|
helper.stack = Gtk.Stack()
|
|
cat_label = Gtk.Label(label="Categories")
|
|
det_label = Gtk.Label(label="Detail")
|
|
helper.stack.add_named(cat_label, "categories")
|
|
helper.stack.add_named(det_label, "detail")
|
|
|
|
# Show the stack to ensure children are realized
|
|
helper.stack.show_all() # pylint: disable=no-member
|
|
helper.stack.set_visible_child_name("detail")
|
|
|
|
button = Gtk.Button()
|
|
button.set_sensitive(False)
|
|
helper.register_disable_widgets(button)
|
|
|
|
helper.show_categories()
|
|
assert button.get_sensitive() is True
|
|
# Verify the visible child is the categories child
|
|
assert helper.stack.get_visible_child() is cat_label
|
|
|
|
def test_stacked_preferences_helper_show_detail(self) -> None:
|
|
"""Test StackedPreferencesHelper show_detail disables registered widgets."""
|
|
|
|
helper = StackedPreferencesHelper()
|
|
helper.stack = Gtk.Stack()
|
|
cat_label = Gtk.Label(label="Categories")
|
|
det_label = Gtk.Label(label="Detail")
|
|
helper.stack.add_named(cat_label, "categories")
|
|
helper.stack.add_named(det_label, "detail")
|
|
|
|
# Show the stack to ensure children are realized
|
|
helper.stack.show_all() # pylint: disable=no-member
|
|
helper.stack.set_visible_child_name("categories")
|
|
|
|
button = Gtk.Button()
|
|
button.set_sensitive(True)
|
|
helper.register_disable_widgets(button)
|
|
|
|
helper.show_detail()
|
|
assert button.get_sensitive() is False
|
|
# Verify the visible child is the detail child
|
|
assert helper.stack.get_visible_child() is det_label
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestFocusManagedListBox:
|
|
"""Test FocusManagedListBox focus management."""
|
|
|
|
def test_focus_managed_listbox_initial_state(self) -> None:
|
|
"""Test FocusManagedListBox initializes with correct settings."""
|
|
|
|
listbox = FocusManagedListBox()
|
|
assert listbox.get_selection_mode() == Gtk.SelectionMode.NONE
|
|
assert listbox.get_can_focus() is False
|
|
|
|
def test_focus_managed_listbox_add_row_with_widget(self) -> None:
|
|
"""Test FocusManagedListBox tracks added rows and widgets."""
|
|
|
|
listbox = FocusManagedListBox()
|
|
row = Gtk.ListBoxRow()
|
|
switch = Gtk.Switch()
|
|
|
|
listbox.add_row_with_widget(row, switch)
|
|
|
|
assert len(listbox._widgets) == 1
|
|
assert len(listbox._rows) == 1
|
|
assert listbox._widgets[0] is switch
|
|
assert listbox._rows[0] is row
|
|
|
|
def test_focus_managed_listbox_get_last_row(self) -> None:
|
|
"""Test FocusManagedListBox.get_last_row returns last added row."""
|
|
|
|
listbox = FocusManagedListBox()
|
|
assert listbox.get_last_row() is None
|
|
|
|
row1 = Gtk.ListBoxRow()
|
|
row2 = Gtk.ListBoxRow()
|
|
listbox.add_row_with_widget(row1, Gtk.Switch())
|
|
listbox.add_row_with_widget(row2, Gtk.Switch())
|
|
|
|
assert listbox.get_last_row() is row2
|
|
|
|
def test_focus_managed_listbox_multiple_rows(self) -> None:
|
|
"""Test FocusManagedListBox handles multiple rows correctly."""
|
|
|
|
listbox = FocusManagedListBox()
|
|
|
|
for _i in range(5):
|
|
row = Gtk.ListBoxRow()
|
|
switch = Gtk.Switch()
|
|
listbox.add_row_with_widget(row, switch)
|
|
|
|
assert len(listbox._widgets) == 5
|
|
assert len(listbox._rows) == 5
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPreferencesGridBase:
|
|
"""Test PreferencesGridBase UI helper methods."""
|
|
|
|
def test_preferences_grid_base_initialization(self) -> None:
|
|
"""Test PreferencesGridBase initializes with correct properties."""
|
|
|
|
grid = PreferencesGridBase("Test Tab")
|
|
assert grid._tab_label == "Test Tab"
|
|
assert grid._has_unsaved_changes is False
|
|
assert grid.get_border_width() == 24 # pylint: disable=no-member
|
|
|
|
def test_preferences_grid_base_get_label(self) -> None:
|
|
"""Test PreferencesGridBase.get_label returns Gtk.Label."""
|
|
|
|
grid = PreferencesGridBase("My Tab")
|
|
label = grid.get_label()
|
|
|
|
assert isinstance(label, Gtk.Label)
|
|
assert label.get_text() == "My Tab"
|
|
|
|
def test_preferences_grid_base_has_changes(self) -> None:
|
|
"""Test PreferencesGridBase.has_changes tracks unsaved changes."""
|
|
|
|
grid = PreferencesGridBase("Test")
|
|
assert grid.has_changes() is False
|
|
|
|
grid._has_unsaved_changes = True
|
|
assert grid.has_changes() is True
|
|
|
|
def test_create_frame_returns_frame_and_grid(self) -> None:
|
|
"""Test _create_frame returns Gtk.Frame and content Gtk.Grid."""
|
|
|
|
grid = PreferencesGridBase("Test")
|
|
frame, content = grid._create_frame("Section Label")
|
|
|
|
assert isinstance(frame, Gtk.Frame)
|
|
assert isinstance(content, Gtk.Grid)
|
|
assert frame.get_child() is content
|
|
|
|
def test_create_label_with_mnemonic(self) -> None:
|
|
"""Test _create_label creates label with mnemonic support."""
|
|
|
|
grid = PreferencesGridBase("Test")
|
|
label = grid._create_label("_Test Label")
|
|
|
|
assert isinstance(label, Gtk.Label)
|
|
# Label should use underline for mnemonic
|
|
assert label.get_use_underline() is True
|
|
|
|
def test_create_switch_row(self) -> None:
|
|
"""Test _create_switch_row creates row with label and switch."""
|
|
|
|
grid = PreferencesGridBase("Test")
|
|
handler_called = [False]
|
|
|
|
def handler(_switch, _param):
|
|
handler_called[0] = True
|
|
|
|
row, switch, label = grid._create_switch_row("Enable Feature", handler, state=True)
|
|
|
|
assert isinstance(row, Gtk.ListBoxRow)
|
|
assert isinstance(switch, Gtk.Switch)
|
|
assert isinstance(label, Gtk.Label)
|
|
assert switch.get_active() is True
|
|
|
|
def test_create_slider_row(self) -> None:
|
|
"""Test _create_slider_row creates row with label and scale."""
|
|
|
|
grid = PreferencesGridBase("Test")
|
|
adjustment = Gtk.Adjustment(
|
|
value=50,
|
|
lower=0,
|
|
upper=100,
|
|
step_increment=1,
|
|
page_increment=10,
|
|
)
|
|
|
|
row, scale, label = grid._create_slider_row("Volume", adjustment)
|
|
|
|
assert isinstance(row, Gtk.ListBoxRow)
|
|
assert isinstance(scale, Gtk.Scale)
|
|
assert isinstance(label, Gtk.Label)
|
|
assert scale.get_value() == 50
|
|
|
|
def test_create_info_listbox(self) -> None:
|
|
"""Test _create_info_listbox creates listbox with info message."""
|
|
|
|
grid = PreferencesGridBase("Test")
|
|
listbox = grid._create_info_listbox("This is an informational message.")
|
|
|
|
assert isinstance(listbox, Gtk.ListBox)
|
|
assert listbox.get_selection_mode() == Gtk.SelectionMode.NONE
|
|
|
|
def test_create_scrolled_window(self) -> None:
|
|
"""Test _create_scrolled_window wraps widget in scrolled container."""
|
|
|
|
grid = PreferencesGridBase("Test")
|
|
label = Gtk.Label(label="Content")
|
|
scrolled = grid._create_scrolled_window(label)
|
|
|
|
assert isinstance(scrolled, Gtk.ScrolledWindow)
|
|
assert scrolled.get_hexpand() is True
|
|
assert scrolled.get_vexpand() is True
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestAutoPreferencesGrid:
|
|
"""Test AutoPreferencesGrid automatic UI building."""
|
|
|
|
def _create_test_grid(self) -> tuple:
|
|
"""Create a test grid with various controls for testing."""
|
|
|
|
values: dict[str, Any] = {
|
|
"bool_setting": True,
|
|
"int_setting": 5,
|
|
"float_setting": 0.5,
|
|
"enum_setting": "Option B",
|
|
}
|
|
|
|
def set_bool(x: bool) -> bool:
|
|
values["bool_setting"] = x
|
|
return True
|
|
|
|
def set_int(x: int) -> bool:
|
|
values["int_setting"] = x
|
|
return True
|
|
|
|
def set_float(x: float) -> bool:
|
|
values["float_setting"] = x
|
|
return True
|
|
|
|
def set_enum(x: str) -> bool:
|
|
values["enum_setting"] = x
|
|
return True
|
|
|
|
controls: list[ControlType] = [
|
|
BooleanPreferenceControl(
|
|
label="Enable Feature",
|
|
getter=lambda: values["bool_setting"],
|
|
setter=set_bool,
|
|
prefs_key="boolSetting",
|
|
),
|
|
IntRangePreferenceControl(
|
|
label="Count",
|
|
minimum=0,
|
|
maximum=10,
|
|
getter=lambda: values["int_setting"],
|
|
setter=set_int,
|
|
prefs_key="intSetting",
|
|
),
|
|
FloatRangePreferenceControl(
|
|
label="Rate",
|
|
minimum=0.0,
|
|
maximum=1.0,
|
|
getter=lambda: values["float_setting"],
|
|
setter=set_float,
|
|
prefs_key="floatSetting",
|
|
),
|
|
EnumPreferenceControl(
|
|
label="Style",
|
|
options=["Option A", "Option B", "Option C"],
|
|
getter=lambda: values["enum_setting"],
|
|
setter=set_enum,
|
|
prefs_key="enumSetting",
|
|
),
|
|
]
|
|
|
|
grid = AutoPreferencesGrid("Test Tab", controls)
|
|
return grid, values, controls
|
|
|
|
def test_auto_preferences_grid_builds_widgets(self) -> None:
|
|
"""Test AutoPreferencesGrid creates widgets for all controls."""
|
|
|
|
grid, _values, controls = self._create_test_grid()
|
|
|
|
assert len(grid._widgets) == len(controls)
|
|
assert len(grid._controls) == len(controls)
|
|
|
|
def test_auto_preferences_grid_boolean_control_creates_switch(self) -> None:
|
|
"""Test AutoPreferencesGrid creates Switch for boolean control."""
|
|
|
|
grid, _values, _controls = self._create_test_grid()
|
|
|
|
# First control is boolean, should create a Switch
|
|
widget = grid.get_widget(0)
|
|
assert isinstance(widget, Gtk.Switch)
|
|
assert widget.get_active() is True
|
|
|
|
def test_auto_preferences_grid_int_range_creates_spinbutton(self) -> None:
|
|
"""Test AutoPreferencesGrid creates SpinButton for int range control."""
|
|
|
|
grid, _values, _controls = self._create_test_grid()
|
|
|
|
widget = grid.get_widget(1)
|
|
assert isinstance(widget, Gtk.SpinButton)
|
|
assert int(widget.get_value()) == 5
|
|
|
|
def test_auto_preferences_grid_float_range_creates_scale(self) -> None:
|
|
"""Test AutoPreferencesGrid creates Scale for float range control."""
|
|
|
|
grid, _values, _controls = self._create_test_grid()
|
|
|
|
widget = grid.get_widget(2)
|
|
assert isinstance(widget, Gtk.Scale)
|
|
assert widget.get_value() == pytest.approx(0.5, abs=0.01)
|
|
|
|
def test_auto_preferences_grid_enum_creates_combobox(self) -> None:
|
|
"""Test AutoPreferencesGrid creates ComboBoxText for enum control."""
|
|
|
|
grid, _values, _controls = self._create_test_grid()
|
|
|
|
widget = grid.get_widget(3)
|
|
assert isinstance(widget, Gtk.ComboBoxText)
|
|
# Option B is at index 1
|
|
assert widget.get_active() == 1
|
|
|
|
def test_auto_preferences_grid_refresh_updates_widgets(self) -> None:
|
|
"""Test AutoPreferencesGrid.refresh updates widget values from getters."""
|
|
|
|
current_value = [True]
|
|
|
|
controls = [
|
|
BooleanPreferenceControl(
|
|
label="Test",
|
|
getter=lambda: current_value[0],
|
|
setter=lambda x: True,
|
|
),
|
|
]
|
|
|
|
grid = AutoPreferencesGrid("Test", controls)
|
|
switch = grid.get_widget(0)
|
|
assert switch and switch.get_active()
|
|
|
|
current_value[0] = False
|
|
grid.refresh()
|
|
assert switch and not switch.get_active()
|
|
|
|
def test_auto_preferences_grid_save_settings_calls_setters(self) -> None:
|
|
"""Test AutoPreferencesGrid.save_settings calls all setters."""
|
|
|
|
grid, values, _controls = self._create_test_grid()
|
|
|
|
switch = grid.get_widget(0)
|
|
switch.set_active(False)
|
|
spin = grid.get_widget(1)
|
|
spin.set_value(8)
|
|
result = grid.save_settings()
|
|
|
|
assert values["bool_setting"] is False
|
|
assert values["int_setting"] == 8
|
|
assert "boolSetting" in result
|
|
assert result["boolSetting"] is False
|
|
|
|
def test_auto_preferences_grid_save_returns_prefs_dict(self) -> None:
|
|
"""Test AutoPreferencesGrid.save_settings returns dict with prefs_keys."""
|
|
|
|
grid, _values, _controls = self._create_test_grid()
|
|
|
|
result = grid.save_settings()
|
|
|
|
assert "boolSetting" in result
|
|
assert "intSetting" in result
|
|
assert "floatSetting" in result
|
|
assert "enumSetting" in result
|
|
|
|
def test_auto_preferences_grid_reload_clears_changes_flag(self) -> None:
|
|
"""Test AutoPreferencesGrid.reload clears unsaved changes flag."""
|
|
|
|
grid, _values, _controls = self._create_test_grid()
|
|
|
|
grid._has_unsaved_changes = True
|
|
grid.reload()
|
|
|
|
assert grid._has_unsaved_changes is False
|
|
|
|
def test_auto_preferences_grid_get_widget_returns_none_for_invalid_index(self) -> None:
|
|
"""Test AutoPreferencesGrid.get_widget returns None for out-of-range index."""
|
|
|
|
grid, _values, controls = self._create_test_grid()
|
|
|
|
assert grid.get_widget(-1) is None
|
|
assert grid.get_widget(len(controls)) is None
|
|
assert grid.get_widget(100) is None
|
|
|
|
def test_auto_preferences_grid_get_widget_for_control(self) -> None:
|
|
"""Test AutoPreferencesGrid.get_widget_for_control finds correct widget."""
|
|
|
|
grid, _values, controls = self._create_test_grid()
|
|
|
|
widget = grid.get_widget_for_control(controls[0])
|
|
assert widget is not None
|
|
assert isinstance(widget, Gtk.Switch)
|
|
|
|
# Non-existent control should return None
|
|
other_control = BooleanPreferenceControl(
|
|
label="Other",
|
|
getter=lambda: True,
|
|
setter=lambda x: True,
|
|
)
|
|
assert grid.get_widget_for_control(other_control) is None
|
|
|
|
def test_auto_preferences_grid_with_grouped_controls(self) -> None:
|
|
"""Test AutoPreferencesGrid groups controls by member_of field."""
|
|
|
|
controls = [
|
|
BooleanPreferenceControl(label="Ungrouped", getter=lambda: True, setter=lambda x: True),
|
|
BooleanPreferenceControl(
|
|
label="Group A Item 1",
|
|
getter=lambda: True,
|
|
setter=lambda x: True,
|
|
member_of="Group A",
|
|
),
|
|
BooleanPreferenceControl(
|
|
label="Group A Item 2",
|
|
getter=lambda: False,
|
|
setter=lambda x: True,
|
|
member_of="Group A",
|
|
),
|
|
BooleanPreferenceControl(
|
|
label="Group B Item",
|
|
getter=lambda: True,
|
|
setter=lambda x: True,
|
|
member_of="Group B",
|
|
),
|
|
]
|
|
|
|
grid = AutoPreferencesGrid("Test", controls)
|
|
|
|
assert "Group A" in grid._group_labels
|
|
assert "Group B" in grid._group_labels
|
|
assert len(grid._widgets) == 4
|
|
|
|
def test_auto_preferences_grid_sensitivity_callback(self) -> None:
|
|
"""Test AutoPreferencesGrid updates sensitivity based on callback."""
|
|
|
|
primary_enabled = [True]
|
|
|
|
def set_primary(x: bool) -> bool:
|
|
primary_enabled[0] = x
|
|
return True
|
|
|
|
controls = [
|
|
BooleanPreferenceControl(
|
|
label="Primary",
|
|
getter=lambda: primary_enabled[0],
|
|
setter=set_primary,
|
|
apply_immediately=True,
|
|
),
|
|
BooleanPreferenceControl(
|
|
label="Dependent",
|
|
getter=lambda: True,
|
|
setter=lambda x: True,
|
|
determine_sensitivity=lambda: primary_enabled[0],
|
|
),
|
|
]
|
|
|
|
grid = AutoPreferencesGrid("Test", controls)
|
|
|
|
primary_switch = grid.get_widget(0)
|
|
dependent_switch = grid.get_widget(1)
|
|
|
|
assert dependent_switch and dependent_switch.get_sensitive()
|
|
|
|
grid._initializing = False # Allow change handlers to run
|
|
assert primary_switch
|
|
primary_switch.set_active(False)
|
|
|
|
assert dependent_switch and not dependent_switch.get_sensitive()
|
|
|
|
def test_auto_preferences_grid_with_info_message(self) -> None:
|
|
"""Test AutoPreferencesGrid displays info message when provided."""
|
|
|
|
controls = [
|
|
BooleanPreferenceControl(label="Test", getter=lambda: True, setter=lambda x: True),
|
|
]
|
|
|
|
grid = AutoPreferencesGrid("Test", controls, info_message="This is helpful information.")
|
|
|
|
assert grid._info_listbox is not None
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestAutoPreferencesGridSelectionControl:
|
|
"""Test AutoPreferencesGrid with SelectionPreferenceControl."""
|
|
|
|
def test_selection_control_creates_combobox_without_actions(self) -> None:
|
|
"""Test SelectionPreferenceControl without actions creates ComboBoxText."""
|
|
|
|
controls = [
|
|
SelectionPreferenceControl(
|
|
label="Choose",
|
|
options=["A", "B", "C"],
|
|
getter=lambda: "B",
|
|
setter=lambda x: True,
|
|
),
|
|
]
|
|
|
|
grid = AutoPreferencesGrid("Test", controls)
|
|
widget = grid.get_widget(0)
|
|
|
|
assert isinstance(widget, Gtk.ComboBoxText)
|
|
assert widget.get_active() == 1
|
|
|
|
def test_selection_control_creates_radio_buttons_with_actions(self) -> None:
|
|
"""Test SelectionPreferenceControl with actions creates RadioButtons."""
|
|
|
|
def get_actions(_value):
|
|
return [("Edit", "edit-symbolic", lambda: None)]
|
|
|
|
controls = [
|
|
SelectionPreferenceControl(
|
|
label="Profile",
|
|
options=["Default", "Custom"],
|
|
getter=lambda: "Default",
|
|
setter=lambda x: True,
|
|
get_actions_for_option=get_actions,
|
|
),
|
|
]
|
|
|
|
grid = AutoPreferencesGrid("Test", controls)
|
|
widget = grid.get_widget(0)
|
|
|
|
assert isinstance(widget, RadioButtonWithActions)
|
|
|
|
def test_selection_control_with_values_mapping(self) -> None:
|
|
"""Test SelectionPreferenceControl maps display options to values."""
|
|
|
|
current_value = [100]
|
|
|
|
def set_level(x: int) -> bool:
|
|
current_value[0] = x
|
|
return True
|
|
|
|
controls = [
|
|
SelectionPreferenceControl(
|
|
label="Level",
|
|
options=["Low", "Medium", "High"],
|
|
values=[50, 100, 150],
|
|
getter=lambda: current_value[0],
|
|
setter=set_level,
|
|
prefs_key="level",
|
|
),
|
|
]
|
|
|
|
grid = AutoPreferencesGrid("Test", controls)
|
|
widget = grid.get_widget(0)
|
|
|
|
assert widget and widget.get_active() == 1
|
|
|
|
widget.set_active(2)
|
|
result = grid.save_settings()
|
|
|
|
assert current_value[0] == 150
|
|
assert result["level"] == 150
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPreferencesGridBaseMultiPageStack:
|
|
"""Test PreferencesGridBase multi-page stack functionality."""
|
|
|
|
def test_create_multi_page_stack_basic(self) -> None:
|
|
"""Test _create_multi_page_stack creates stack with categories."""
|
|
|
|
class TestGrid(PreferencesGridBase):
|
|
"""Test grid."""
|
|
|
|
def __init__(self):
|
|
super().__init__("Test")
|
|
self._initializing = True
|
|
|
|
child1 = PreferencesGridBase("Child 1")
|
|
child2 = PreferencesGridBase("Child 2")
|
|
|
|
enable_listbox, stack, categories = self._create_multi_page_stack(
|
|
enable_label="Enable",
|
|
enable_getter=lambda: True,
|
|
enable_setter=lambda x: True,
|
|
categories=[
|
|
("General", "general", child1),
|
|
("Advanced", "advanced", child2),
|
|
],
|
|
main_title="Test Settings",
|
|
)
|
|
|
|
self.my_enable_listbox = enable_listbox
|
|
self.my_stack = stack
|
|
self.my_categories = categories
|
|
self._initializing = False
|
|
|
|
grid = TestGrid()
|
|
|
|
assert grid.my_stack is not None
|
|
assert grid.my_categories is not None
|
|
assert grid.my_enable_listbox is not None
|
|
|
|
def test_create_multi_page_stack_without_enable(self) -> None:
|
|
"""Test _create_multi_page_stack without enable switch."""
|
|
|
|
class TestGrid(PreferencesGridBase):
|
|
"""Test grid."""
|
|
|
|
def __init__(self):
|
|
super().__init__("Test")
|
|
self._initializing = True
|
|
|
|
child = PreferencesGridBase("Child")
|
|
|
|
enable_listbox, stack, _categories = self._create_multi_page_stack(
|
|
enable_label=None,
|
|
enable_getter=None,
|
|
enable_setter=None,
|
|
categories=[("General", "general", child)],
|
|
main_title="Test",
|
|
)
|
|
|
|
self.my_enable_listbox = enable_listbox
|
|
self.my_stack = stack
|
|
self._initializing = False
|
|
|
|
grid = TestGrid()
|
|
|
|
assert grid.my_enable_listbox is None
|
|
assert grid.my_stack is not None
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestPreferencesGridBaseStackedPreferences:
|
|
"""Test PreferencesGridBase stacked drill-down preferences."""
|
|
|
|
def test_create_stacked_preferences(self) -> None:
|
|
"""Test _create_stacked_preferences creates stack with categories and detail."""
|
|
|
|
class TestGrid(PreferencesGridBase):
|
|
"""Test grid."""
|
|
|
|
def __init__(self):
|
|
super().__init__("Test")
|
|
self.activated_row = None
|
|
|
|
stack, categories, detail = self._create_stacked_preferences(
|
|
on_category_activated=self._on_category,
|
|
on_detail_row_activated=None,
|
|
)
|
|
self.my_stack = stack
|
|
self.my_categories = categories
|
|
self.my_detail = detail
|
|
|
|
def _on_category(self, row):
|
|
self.activated_row = row
|
|
|
|
grid = TestGrid()
|
|
|
|
assert grid.my_stack is not None
|
|
assert grid.my_categories is not None
|
|
assert grid.my_detail is not None
|
|
assert isinstance(grid.my_stack, Gtk.Stack)
|
|
assert isinstance(grid.my_categories, Gtk.ListBox)
|
|
assert isinstance(grid.my_detail, Gtk.ListBox)
|
|
|
|
def test_add_stack_category_row(self) -> None:
|
|
"""Test _add_stack_category_row adds category with correct properties."""
|
|
|
|
class TestGrid(PreferencesGridBase):
|
|
"""Test grid."""
|
|
|
|
def __init__(self):
|
|
super().__init__("Test")
|
|
_stack, categories, _detail = self._create_stacked_preferences(
|
|
on_category_activated=lambda r: None,
|
|
)
|
|
self.my_categories = categories
|
|
|
|
grid = TestGrid()
|
|
|
|
row = grid._add_stack_category_row(grid.my_categories, "Speech Settings", category="speech")
|
|
|
|
assert isinstance(row, CategoryListBoxRow)
|
|
assert row.category == "speech"
|
|
|
|
def test_add_stack_category_row_chevron_accessible_role(self) -> None:
|
|
"""Test _add_stack_category_row chevron has PUSH_BUTTON accessible role."""
|
|
|
|
gi.require_version("Atk", "1.0")
|
|
from gi.repository import Atk
|
|
|
|
class TestGrid(PreferencesGridBase):
|
|
"""Test grid."""
|
|
|
|
def __init__(self):
|
|
super().__init__("Test")
|
|
_stack, categories, _detail = self._create_stacked_preferences(
|
|
on_category_activated=lambda r: None,
|
|
)
|
|
self.my_categories = categories
|
|
|
|
grid = TestGrid()
|
|
row = grid._add_stack_category_row(grid.my_categories, "Test Category", category="test")
|
|
|
|
# Find the chevron image in the row's hbox
|
|
hbox = row.get_child()
|
|
chevron = None
|
|
for child in hbox.get_children():
|
|
if isinstance(child, Gtk.Image):
|
|
chevron = child
|
|
break
|
|
|
|
assert chevron is not None, "Chevron image not found in category row"
|
|
|
|
chevron_accessible = chevron.get_accessible()
|
|
assert chevron_accessible is not None
|
|
assert chevron_accessible.get_role() == Atk.Role.BUTTON
|
|
assert chevron_accessible.get_name() == ""
|
|
|
|
def test_show_stack_categories_and_detail(self) -> None:
|
|
"""Test _show_stack_categories and _show_stack_detail switch views."""
|
|
|
|
class TestGrid(PreferencesGridBase):
|
|
"""Test grid."""
|
|
|
|
def __init__(self):
|
|
super().__init__("Test")
|
|
stack, categories, detail = self._create_stacked_preferences(
|
|
on_category_activated=lambda r: None,
|
|
)
|
|
self.my_stack = stack
|
|
self.my_categories = categories
|
|
self.my_detail = detail
|
|
|
|
grid = TestGrid()
|
|
|
|
grid.my_stack.show_all() # pylint: disable=no-member
|
|
|
|
cat_scrolled = grid.my_stack.get_child_by_name("categories")
|
|
det_scrolled = grid.my_stack.get_child_by_name("detail")
|
|
|
|
grid._show_stack_detail()
|
|
assert grid.my_stack.get_visible_child() is det_scrolled
|
|
|
|
grid._show_stack_categories()
|
|
assert grid.my_stack.get_visible_child() is cat_scrolled
|