You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
690 lines
24 KiB
690 lines
24 KiB
#!/usr/bin/env python3 |
|
# MIT License |
|
# |
|
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
|
# |
|
# Permission is hereby granted, free of charge, to any person obtaining a copy |
|
# of this software and associated documentation files (the "Software"), to deal |
|
# in the Software without restriction, including without limitation the rights |
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
# copies of the Software, and to permit persons to whom the Software is |
|
# furnished to do so, subject to the following conditions: |
|
# |
|
# The above copyright notice and this permission notice shall be included in all |
|
# copies or substantial portions of the Software. |
|
# |
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
# SOFTWARE. |
|
|
|
import os |
|
import sys |
|
import pytest |
|
from unittest.mock import Mock, MagicMock, patch |
|
|
|
sys.path.append(os.path.join(sys.path[0],'../../../libs/pyTermTk')) |
|
import TermTk as ttk |
|
|
|
|
|
class TestTTkTableWidget: |
|
"""Test cases for TTkTableWidget class""" |
|
|
|
def setup_method(self): |
|
"""Set up test fixtures before each test method.""" |
|
self.test_data = [ |
|
['Alice', 25, 'Engineer'], |
|
['Bob', 30, 'Designer'], |
|
['Charlie', 35, 'Manager'] |
|
] |
|
self.header = ['Name', 'Age', 'Role'] |
|
self.indexes = ['Row1', 'Row2', 'Row3'] |
|
|
|
# Create a basic table model for testing |
|
self.table_model = ttk.TTkTableModelList( |
|
data=self.test_data, |
|
header=self.header, |
|
indexes=self.indexes |
|
) |
|
|
|
def test_init_default(self): |
|
"""Test default initialization""" |
|
widget = ttk.TTkTableWidget() |
|
|
|
assert widget is not None |
|
assert widget.model() is not None |
|
assert isinstance(widget.model(), ttk.TTkAbstractTableModel) |
|
# Default model should have 10x10 grid with empty strings |
|
assert widget.rowCount() == 10 |
|
assert widget.columnCount() == 10 |
|
|
|
def test_init_with_model(self): |
|
"""Test initialization with a table model""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
assert widget.model() == self.table_model |
|
assert widget.rowCount() == 3 |
|
assert widget.columnCount() == 3 |
|
|
|
def test_init_with_separator_options(self): |
|
"""Test initialization with separator visibility options""" |
|
widget = ttk.TTkTableWidget( |
|
vSeparator=False, |
|
hSeparator=False |
|
) |
|
|
|
assert not widget.vSeparatorVisibility() |
|
assert not widget.hSeparatorVisibility() |
|
|
|
def test_init_with_header_options(self): |
|
"""Test initialization with header visibility options""" |
|
widget = ttk.TTkTableWidget( |
|
vHeader=False, |
|
hHeader=False |
|
) |
|
|
|
assert not widget.verticalHeader().isVisible() |
|
assert not widget.horizontalHeader().isVisible() |
|
|
|
def test_init_with_sorting_enabled(self): |
|
"""Test initialization with sorting enabled""" |
|
widget = ttk.TTkTableWidget(sortingEnabled=True) |
|
|
|
assert widget.isSortingEnabled() |
|
|
|
def test_init_with_data_padding(self): |
|
"""Test initialization with custom data padding""" |
|
widget = ttk.TTkTableWidget(dataPadding=3) |
|
|
|
# Data padding is internal, but we can verify the widget was created |
|
assert widget is not None |
|
|
|
def test_model_getter_setter(self): |
|
"""Test model getter and setter""" |
|
widget = ttk.TTkTableWidget() |
|
original_model = widget.model() |
|
|
|
# Set new model |
|
widget.setModel(self.table_model) |
|
assert widget.model() == self.table_model |
|
assert widget.model() != original_model |
|
|
|
# Verify model data is accessible |
|
assert widget.rowCount() == 3 |
|
assert widget.columnCount() == 3 |
|
|
|
def test_row_column_count(self): |
|
"""Test row and column count methods""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
assert widget.rowCount() == 3 |
|
assert widget.columnCount() == 3 |
|
|
|
def test_current_row_column(self): |
|
"""Test current row and column methods""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Initially should be at (0, 0) or (-1, -1) if no selection |
|
current_row = widget.currentRow() |
|
current_col = widget.currentColumn() |
|
|
|
assert isinstance(current_row, int) |
|
assert isinstance(current_col, int) |
|
assert current_row >= -1 |
|
assert current_col >= -1 |
|
|
|
def test_header_views(self): |
|
"""Test vertical and horizontal header views""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
v_header = widget.verticalHeader() |
|
h_header = widget.horizontalHeader() |
|
|
|
assert isinstance(v_header, ttk.TTkHeaderView) |
|
assert isinstance(h_header, ttk.TTkHeaderView) |
|
assert v_header.isVisible() |
|
assert h_header.isVisible() |
|
|
|
def test_separator_visibility(self): |
|
"""Test separator visibility methods""" |
|
widget = ttk.TTkTableWidget() |
|
|
|
# Default should be visible |
|
assert widget.hSeparatorVisibility() |
|
assert widget.vSeparatorVisibility() |
|
|
|
# Test setters |
|
widget.setHSeparatorVisibility(False) |
|
widget.setVSeparatorVisibility(False) |
|
|
|
assert not widget.hSeparatorVisibility() |
|
assert not widget.vSeparatorVisibility() |
|
|
|
# Test setting back to visible |
|
widget.setHSeparatorVisibility(True) |
|
widget.setVSeparatorVisibility(True) |
|
|
|
assert widget.hSeparatorVisibility() |
|
assert widget.vSeparatorVisibility() |
|
|
|
def test_sorting_functionality(self): |
|
"""Test sorting enabled/disabled and sort by column""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Initially sorting should be disabled |
|
assert not widget.isSortingEnabled() |
|
|
|
# Enable sorting |
|
widget.setSortingEnabled(True) |
|
assert widget.isSortingEnabled() |
|
|
|
# Test sorting by column |
|
widget.sortByColumn(0, ttk.TTkK.SortOrder.AscendingOrder) |
|
# Note: We can't easily verify the actual sorting without checking the model state |
|
|
|
# Disable sorting |
|
widget.setSortingEnabled(False) |
|
assert not widget.isSortingEnabled() |
|
|
|
def test_selection_methods(self): |
|
"""Test selection-related methods""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Test clear selection |
|
widget.clearSelection() |
|
|
|
# Test select all |
|
widget.selectAll() |
|
|
|
# Test select row |
|
widget.selectRow(0) |
|
|
|
# Test select column |
|
widget.selectColumn(1) |
|
|
|
# Test set selection with position and size |
|
widget.setSelection((0, 0), (2, 2), ttk.TTkK.TTkItemSelectionModel.Select) |
|
|
|
# These methods should not raise exceptions |
|
# Actual selection state testing would require more complex setup |
|
|
|
def test_column_width_operations(self): |
|
"""Test column width setting and resizing""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Test setting column width |
|
widget.setColumnWidth(0, 20) |
|
|
|
# Test resize column to contents |
|
widget.resizeColumnToContents(0) |
|
|
|
# Test resize all columns to contents |
|
widget.resizeColumnsToContents() |
|
|
|
# These methods should not raise exceptions |
|
|
|
def test_row_height_operations(self): |
|
"""Test row height setting and resizing""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Test setting row height |
|
widget.setRowHeight(0, 3) |
|
|
|
# Test resize row to contents |
|
widget.resizeRowToContents(0) |
|
|
|
# Test resize all rows to contents |
|
widget.resizeRowsToContents() |
|
|
|
# These methods should not raise exceptions |
|
|
|
def test_clipboard_operations(self): |
|
"""Test copy, cut, and paste operations""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# These operations should not raise exceptions |
|
widget.copy() |
|
widget.cut() |
|
widget.paste() |
|
|
|
def test_undo_redo_operations(self): |
|
"""Test undo and redo operations""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Initially, there should be no undo/redo available |
|
# But the methods should not raise exceptions |
|
widget.undo() |
|
widget.redo() |
|
|
|
def test_signals_exist(self): |
|
"""Test that all expected signals exist""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Test that signals are accessible |
|
assert hasattr(widget, 'cellChanged') |
|
assert hasattr(widget, 'cellClicked') |
|
assert hasattr(widget, 'cellDoubleClicked') |
|
assert hasattr(widget, 'cellEntered') |
|
assert hasattr(widget, 'currentCellChanged') |
|
|
|
# Test that they are signal objects |
|
assert isinstance(widget.cellChanged, ttk.pyTTkSignal) |
|
assert isinstance(widget.cellClicked, ttk.pyTTkSignal) |
|
assert isinstance(widget.cellDoubleClicked, ttk.pyTTkSignal) |
|
assert isinstance(widget.cellEntered, ttk.pyTTkSignal) |
|
assert isinstance(widget.currentCellChanged, ttk.pyTTkSignal) |
|
|
|
def test_signal_connections(self): |
|
"""Test signal connections""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Create mock slots |
|
|
|
cell_changed_slot = Mock() |
|
cell_clicked_slot = Mock() |
|
current_cell_changed_slot = Mock() |
|
@ttk.pyTTkSlot(int,int) |
|
def _mock_cell_changed_slot(row:int,col:int): |
|
cell_changed_slot(row, col) |
|
@ttk.pyTTkSlot(int,int) |
|
def _mock_cell_clicked_slot(row:int,col:int): |
|
cell_clicked_slot(row, col) |
|
@ttk.pyTTkSlot(int,int,int,int) |
|
def _mock_cell_cchanged_slot(a:int,b:int,c:int,d:int): |
|
current_cell_changed_slot(a,b,c,d) |
|
|
|
# Connect signals |
|
widget.cellChanged.connect(_mock_cell_changed_slot) |
|
widget.cellClicked.connect(_mock_cell_clicked_slot) |
|
widget.currentCellChanged.connect(_mock_cell_cchanged_slot) |
|
|
|
# Verify connections were made (signals should have connections) |
|
assert len(widget.cellChanged._connected_slots) > 0 |
|
assert len(widget.cellClicked._connected_slots) > 0 |
|
assert len(widget.currentCellChanged._connected_slots) > 0 |
|
|
|
def test_model_change_propagation(self): |
|
"""Test that model changes are propagated to the widget""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Modify the model |
|
self.table_model.setData(0, 0, 'Modified') |
|
|
|
# The widget should be notified (through signal connections) |
|
# We can't easily test the actual update without a more complex setup |
|
assert widget.model().data(0, 0) == 'Modified' |
|
|
|
def test_empty_model_handling(self): |
|
"""Test widget behavior with empty model""" |
|
empty_model = ttk.TTkTableModelList(data=[[]]) |
|
widget = ttk.TTkTableWidget(tableModel=empty_model) |
|
|
|
assert widget.rowCount() == 1 |
|
assert widget.columnCount() == 0 |
|
|
|
def test_large_model_handling(self): |
|
"""Test widget behavior with large model""" |
|
large_data = [[f'Cell_{i}_{j}' for j in range(100)] for i in range(100)] |
|
large_model = ttk.TTkTableModelList(data=large_data) |
|
widget = ttk.TTkTableWidget(tableModel=large_model) |
|
|
|
assert widget.rowCount() == 100 |
|
assert widget.columnCount() == 100 |
|
|
|
def test_model_replacement(self): |
|
"""Test replacing the model after widget creation""" |
|
widget = ttk.TTkTableWidget() |
|
original_row_count = widget.rowCount() |
|
original_col_count = widget.columnCount() |
|
|
|
# Replace with our test model |
|
widget.setModel(self.table_model) |
|
|
|
# Verify the change |
|
assert widget.rowCount() != original_row_count |
|
assert widget.columnCount() != original_col_count |
|
assert widget.rowCount() == 3 |
|
assert widget.columnCount() == 3 |
|
|
|
def test_widget_with_different_model_types(self): |
|
"""Test widget with different types of table models""" |
|
# Test with TTkTableModelList |
|
list_model = ttk.TTkTableModelList(data=self.test_data) |
|
widget1 = ttk.TTkTableWidget(tableModel=list_model) |
|
assert widget1.rowCount() == 3 |
|
|
|
# Test with TTkTableModelCSV (using in-memory data) |
|
import io |
|
csv_data = io.StringIO("Name,Age\nAlice,25\nBob,30") |
|
csv_model = ttk.TTkTableModelCSV(fd=csv_data) |
|
widget2 = ttk.TTkTableWidget(tableModel=csv_model) |
|
assert widget2.rowCount() == 2 |
|
|
|
def test_header_visibility_changes(self): |
|
"""Test changing header visibility after initialization""" |
|
widget = ttk.TTkTableWidget() |
|
|
|
# Initially headers should be visible |
|
assert widget.verticalHeader().isVisible() |
|
assert widget.horizontalHeader().isVisible() |
|
|
|
# Hide headers |
|
widget.verticalHeader().hide() |
|
widget.horizontalHeader().hide() |
|
|
|
assert not widget.verticalHeader().isVisible() |
|
assert not widget.horizontalHeader().isVisible() |
|
|
|
# Show headers again |
|
widget.verticalHeader().show() |
|
widget.horizontalHeader().show() |
|
|
|
assert widget.verticalHeader().isVisible() |
|
assert widget.horizontalHeader().isVisible() |
|
|
|
def test_focus_policy(self): |
|
"""Test that the widget has proper focus policy""" |
|
widget = ttk.TTkTableWidget() |
|
|
|
# Should accept click and tab focus |
|
focus_policy = widget.focusPolicy() |
|
assert focus_policy & ttk.TTkK.ClickFocus |
|
assert focus_policy & ttk.TTkK.TabFocus |
|
|
|
def test_minimum_size(self): |
|
"""Test minimum size constraints""" |
|
widget = ttk.TTkTableWidget() |
|
|
|
# Should have minimum height of 1 |
|
min_size = widget.minimumSize() |
|
assert min_size[1] >= 1 # height should be at least 1 |
|
|
|
def test_style_properties(self): |
|
"""Test that the widget has proper style properties""" |
|
widget = ttk.TTkTableWidget() |
|
|
|
# Should have class style defined |
|
assert hasattr(ttk.TTkTableWidget, 'classStyle') |
|
assert isinstance(ttk.TTkTableWidget.classStyle, dict) |
|
assert 'default' in ttk.TTkTableWidget.classStyle |
|
|
|
def test_boundary_conditions(self): |
|
"""Test boundary conditions and edge cases""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Test with invalid row/column indices (should not crash) |
|
try: |
|
widget.selectRow(-1) |
|
widget.selectRow(1000) |
|
widget.selectColumn(-1) |
|
widget.selectColumn(1000) |
|
widget.setColumnWidth(-1, 10) |
|
widget.setColumnWidth(1000, 10) |
|
widget.setRowHeight(-1, 2) |
|
widget.setRowHeight(1000, 2) |
|
except Exception as e: |
|
# Some operations might raise exceptions, but they shouldn't crash |
|
pass |
|
|
|
def test_integration_with_scroll_area(self): |
|
"""Test that the widget works as expected within a scroll area (via TTkTable)""" |
|
table = ttk.TTkTable(tableModel=self.table_model) |
|
|
|
# TTkTable should forward methods to the internal TTkTableWidget |
|
assert table.rowCount() == 3 |
|
assert table.columnCount() == 3 |
|
assert table.model() == self.table_model |
|
|
|
def test_zero_rows_model(self): |
|
"""Test widget behavior with model that has zero rows""" |
|
# Create model with zero rows but some columns |
|
empty_rows_model = ttk.TTkTableModelList( |
|
data=[], |
|
header=['Col1', 'Col2', 'Col3'] |
|
) |
|
widget = ttk.TTkTableWidget(tableModel=empty_rows_model) |
|
|
|
assert widget.rowCount() == 0 |
|
assert widget.columnCount() == 0 |
|
assert widget.currentRow() == 0 # No valid current row |
|
assert widget.currentColumn() == 0 # No valid current column |
|
|
|
# Selection operations should handle empty rows gracefully |
|
widget.clearSelection() |
|
widget.selectAll() # Should not crash with no rows |
|
|
|
# Column operations should still work |
|
widget.setColumnWidth(0, 50) |
|
widget.resizeColumnsToContents() |
|
|
|
# Row operations should handle empty case |
|
widget.resizeRowsToContents() # Should not crash with no rows |
|
|
|
def test_zero_columns_model(self): |
|
"""Test widget behavior with model that has zero columns""" |
|
# Create model with zero columns but some rows |
|
empty_cols_model = ttk.TTkTableModelList( |
|
data=[[], [], []], # 3 empty rows |
|
header=[] |
|
) |
|
widget = ttk.TTkTableWidget(tableModel=empty_cols_model) |
|
|
|
assert widget.rowCount() == 3 |
|
assert widget.columnCount() == 0 |
|
assert widget.currentRow() == 0 # No valid current row |
|
assert widget.currentColumn() == 0 # No valid current column |
|
|
|
# Selection operations should handle empty columns gracefully |
|
widget.clearSelection() |
|
widget.selectAll() # Should not crash with no columns |
|
|
|
# Row operations should still work |
|
widget.setRowHeight(0, 2) |
|
widget.resizeRowsToContents() |
|
|
|
# Column operations should handle empty case |
|
widget.resizeColumnsToContents() # Should not crash with no columns |
|
|
|
def test_completely_empty_model(self): |
|
"""Test widget behavior with model that has zero rows and zero columns""" |
|
# Create completely empty model |
|
empty_model = ttk.TTkTableModelList( |
|
data=[], |
|
header=[] |
|
) |
|
widget = ttk.TTkTableWidget(tableModel=empty_model) |
|
|
|
assert widget.rowCount() == 0 |
|
assert widget.columnCount() == 0 |
|
assert widget.currentRow() == 0 |
|
assert widget.currentColumn() == 0 |
|
|
|
# All operations should handle completely empty case gracefully |
|
widget.clearSelection() |
|
widget.selectAll() |
|
widget.resizeColumnsToContents() |
|
widget.resizeRowsToContents() |
|
|
|
# Clipboard operations with empty model |
|
widget.copy() # Should not crash |
|
widget.cut() # Should not crash |
|
widget.paste() # Should not crash |
|
|
|
# Undo/redo with empty model |
|
widget.undo() |
|
widget.redo() |
|
|
|
def test_single_cell_model(self): |
|
"""Test widget behavior with model that has exactly one cell""" |
|
single_cell_model = ttk.TTkTableModelList( |
|
data=[['SingleCell']], |
|
header=['OnlyColumn'] |
|
) |
|
widget = ttk.TTkTableWidget(tableModel=single_cell_model) |
|
|
|
assert widget.rowCount() == 1 |
|
assert widget.columnCount() == 1 |
|
|
|
# Selection operations |
|
widget.selectAll() |
|
widget.selectRow(0) |
|
widget.selectColumn(0) |
|
widget.setSelection((0, 0), (1, 1), ttk.TTkK.TTkItemSelectionModel.Select) |
|
|
|
# Resize operations |
|
widget.setColumnWidth(0, 20) |
|
widget.setRowHeight(0, 3) |
|
widget.resizeColumnToContents(0) |
|
widget.resizeRowToContents(0) |
|
|
|
def test_model_transitions(self): |
|
"""Test transitioning between different model sizes""" |
|
widget = ttk.TTkTableWidget(tableModel=self.table_model) |
|
|
|
# Start with normal model |
|
assert widget.rowCount() == 3 |
|
assert widget.columnCount() == 3 |
|
|
|
# Transition to empty rows |
|
empty_rows_model = ttk.TTkTableModelList(data=[], header=['A', 'B']) |
|
widget.setModel(empty_rows_model) |
|
assert widget.rowCount() == 0 |
|
assert widget.columnCount() == 0 |
|
|
|
# Transition to empty columns |
|
empty_cols_model = ttk.TTkTableModelList(data=[[], []], header=[]) |
|
widget.setModel(empty_cols_model) |
|
assert widget.rowCount() == 2 |
|
assert widget.columnCount() == 0 |
|
|
|
# Transition to completely empty |
|
empty_model = ttk.TTkTableModelList(data=[], header=[]) |
|
widget.setModel(empty_model) |
|
assert widget.rowCount() == 0 |
|
assert widget.columnCount() == 0 |
|
|
|
# Transition back to normal model |
|
widget.setModel(self.table_model) |
|
assert widget.rowCount() == 3 |
|
assert widget.columnCount() == 3 |
|
|
|
def test_boundary_operations_on_empty_models(self): |
|
"""Test boundary operations on various empty model configurations""" |
|
|
|
# Test with zero rows model |
|
zero_rows_model = ttk.TTkTableModelList(data=[], header=['A', 'B']) |
|
widget = ttk.TTkTableWidget(tableModel=zero_rows_model) |
|
|
|
# These should not crash even with no rows |
|
try: |
|
widget.selectRow(0) # Invalid row |
|
widget.unselectRow(0) # Invalid row |
|
widget.setRowHeight(0, 2) # Invalid row |
|
widget.resizeRowToContents(0) # Invalid row |
|
except (IndexError, ValueError): |
|
pass # Expected for invalid indices |
|
|
|
# Test with zero columns model |
|
zero_cols_model = ttk.TTkTableModelList(data=[[], []], header=[]) |
|
widget.setModel(zero_cols_model) |
|
|
|
# These should not crash even with no columns |
|
try: |
|
widget.selectColumn(0) # Invalid column |
|
widget.unselectColumn(0) # Invalid column |
|
widget.setColumnWidth(0, 20) # Invalid column |
|
widget.resizeColumnToContents(0) # Invalid column |
|
except (IndexError, ValueError): |
|
pass # Expected for invalid indices |
|
|
|
def test_empty_model_sorting(self): |
|
"""Test sorting operations on empty models""" |
|
|
|
# Test sorting with zero rows |
|
zero_rows_model = ttk.TTkTableModelList(data=[], header=['Name', 'Age']) |
|
widget = ttk.TTkTableWidget(tableModel=zero_rows_model, sortingEnabled=True) |
|
|
|
assert widget.isSortingEnabled() |
|
# Sorting empty data should not crash |
|
widget.sortByColumn(0, ttk.TTkK.SortOrder.AscendingOrder) |
|
widget.sortByColumn(1, ttk.TTkK.SortOrder.DescendingOrder) |
|
|
|
# Test sorting with zero columns |
|
zero_cols_model = ttk.TTkTableModelList(data=[[], []], header=[]) |
|
widget.setModel(zero_cols_model) |
|
|
|
# Sorting with no columns should handle gracefully |
|
try: |
|
widget.sortByColumn(0, ttk.TTkK.SortOrder.AscendingOrder) # Invalid column |
|
except (IndexError, ValueError): |
|
pass # Expected for invalid column index |
|
|
|
def test_empty_model_signals(self): |
|
"""Test that signals work correctly with empty models""" |
|
empty_model = ttk.TTkTableModelList(data=[], header=[]) |
|
widget = ttk.TTkTableWidget(tableModel=empty_model) |
|
|
|
# Connect signals to mock slots |
|
cell_changed_calls = [] |
|
cell_clicked_calls = [] |
|
|
|
@ttk.pyTTkSlot(int, int) |
|
def mock_cell_changed(row, col): |
|
cell_changed_calls.append((row, col)) |
|
|
|
@ttk.pyTTkSlot(int, int) |
|
def mock_cell_clicked(row, col): |
|
cell_clicked_calls.append((row, col)) |
|
|
|
widget.cellChanged.connect(mock_cell_changed) |
|
widget.cellClicked.connect(mock_cell_clicked) |
|
|
|
# Signals should be connected even with empty model |
|
assert len(widget.cellChanged._connected_slots) > 0 |
|
assert len(widget.cellClicked._connected_slots) > 0 |
|
|
|
# No signals should be emitted for empty model operations |
|
widget.clearSelection() |
|
widget.selectAll() |
|
|
|
# Should have no signal calls since there are no cells |
|
assert len(cell_changed_calls) == 0 |
|
assert len(cell_clicked_calls) == 0 |
|
|
|
|
|
def test_integration_with_full_application(): |
|
"""Integration test - verify TTkTableWidget works in a full application context""" |
|
# Create a more complex scenario |
|
data = [ |
|
['Product A', 100, 29.99], |
|
['Product B', 50, 19.99], |
|
['Product C', 75, 39.99] |
|
] |
|
headers = ['Product', 'Stock', 'Price'] |
|
|
|
model = ttk.TTkTableModelList(data=data, header=headers) |
|
widget = ttk.TTkTableWidget( |
|
tableModel=model, |
|
sortingEnabled=True, |
|
vSeparator=True, |
|
hSeparator=True |
|
) |
|
|
|
# Test various operations in sequence |
|
widget.resizeColumnsToContents() |
|
widget.setSortingEnabled(True) |
|
widget.sortByColumn(2, ttk.TTkK.SortOrder.DescendingOrder) # Sort by price descending |
|
widget.selectRow(0) |
|
widget.copy() |
|
|
|
# Verify the widget still functions correctly |
|
assert widget.rowCount() == 3 |
|
assert widget.columnCount() == 3 |
|
assert widget.isSortingEnabled() |
|
|
|
|
|
if __name__ == '__main__': |
|
pytest.main([__file__])
|
|
|