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.
595 lines
16 KiB
595 lines
16 KiB
# 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 sys, os |
|
|
|
sys.path.append(os.path.join(sys.path[0],'../../../libs/pyTermTk')) |
|
import TermTk as ttk |
|
from TermTk.TTkWidgets.kodetab import _TTkKodeTab |
|
from TermTk.TTkWidgets.tabwidget import _TTkTabWidgetDragData, _TTkNewTabWidgetDragData |
|
from TermTk.TTkGui.drag import TTkDnDEvent |
|
|
|
# ============================================================================ |
|
# TTkKodeTab Basic Tests |
|
# ============================================================================ |
|
|
|
def test_kodetab_initialization(): |
|
''' |
|
Test that TTkKodeTab initializes correctly. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
assert kodeTab is not None |
|
assert isinstance(kodeTab, ttk.TTkSplitter) |
|
|
|
# Should have at least one internal _TTkKodeTab widget |
|
first_widget = kodeTab._getFirstWidget() |
|
assert first_widget is not None |
|
assert isinstance(first_widget, _TTkKodeTab) |
|
|
|
def test_kodetab_add_single_tab(): |
|
''' |
|
Test adding a single tab to TTkKodeTab. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget = ttk.TTkWidget() |
|
|
|
index = kodeTab.addTab(widget, "Tab 1") |
|
|
|
assert index == 0 |
|
assert kodeTab._lastKodeTabWidget.count() == 1 |
|
assert kodeTab._lastKodeTabWidget.widget(0) == widget |
|
|
|
def test_kodetab_add_multiple_tabs(): |
|
''' |
|
Test adding multiple tabs to TTkKodeTab. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
widget3 = ttk.TTkWidget() |
|
|
|
index1 = kodeTab.addTab(widget1, "Tab 1") |
|
index2 = kodeTab.addTab(widget2, "Tab 2") |
|
index3 = kodeTab.addTab(widget3, "Tab 3") |
|
|
|
assert index1 == 0 |
|
assert index2 == 1 |
|
assert index3 == 2 |
|
assert kodeTab._lastKodeTabWidget.count() == 3 |
|
|
|
def test_kodetab_add_tab_with_data(): |
|
''' |
|
Test adding tabs with associated data. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
|
|
kodeTab.addTab(widget1, "Tab 1", data="data1") |
|
kodeTab.addTab(widget2, "Tab 2", data={"key": "value"}) |
|
|
|
assert kodeTab._lastKodeTabWidget.tabData(0) == "data1" |
|
assert kodeTab._lastKodeTabWidget.tabData(1) == {"key": "value"} |
|
|
|
def test_kodetab_set_current_widget(): |
|
''' |
|
Test setting the current widget in TTkKodeTab. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
widget3 = ttk.TTkWidget() |
|
|
|
kodeTab.addTab(widget1, "Tab 1") |
|
kodeTab.addTab(widget2, "Tab 2") |
|
kodeTab.addTab(widget3, "Tab 3") |
|
|
|
kodeTab.setCurrentWidget(widget2) |
|
assert kodeTab._lastKodeTabWidget.currentWidget() == widget2 |
|
|
|
kodeTab.setCurrentWidget(widget3) |
|
assert kodeTab._lastKodeTabWidget.currentWidget() == widget3 |
|
|
|
def test_kodetab_iter_items(): |
|
''' |
|
Test iterating over all tabs in TTkKodeTab. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
widget3 = ttk.TTkWidget() |
|
|
|
kodeTab.addTab(widget1, "Tab 1") |
|
kodeTab.addTab(widget2, "Tab 2") |
|
kodeTab.addTab(widget3, "Tab 3") |
|
|
|
items = list(kodeTab.iterItems()) |
|
|
|
assert len(items) == 3 |
|
for tab_widget, index in items: |
|
assert isinstance(tab_widget, _TTkKodeTab) |
|
assert 0 <= index < tab_widget.count() |
|
|
|
def test_kodetab_signals(): |
|
''' |
|
Test that TTkKodeTab signals are emitted correctly. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
current_changed_called = [] |
|
tab_clicked_called = [] |
|
tab_added_called = [] |
|
|
|
kodeTab.currentChanged.connect( |
|
lambda tw, i, w, d: current_changed_called.append((tw, i, w, d)) |
|
) |
|
kodeTab.tabBarClicked.connect( |
|
lambda tw, i, w, d: tab_clicked_called.append((tw, i, w, d)) |
|
) |
|
kodeTab.tabAdded.connect( |
|
lambda tw, i: tab_added_called.append((tw, i)) |
|
) |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
|
|
kodeTab.addTab(widget1, "Tab 1") |
|
assert len(tab_added_called) == 1 |
|
assert len(current_changed_called) == 1 |
|
|
|
kodeTab.addTab(widget2, "Tab 2") |
|
assert len(tab_added_called) == 2 |
|
|
|
def test_kodetab_close_requested_signal(): |
|
''' |
|
Test that kodeTabCloseRequested signal is emitted correctly. |
|
''' |
|
kodeTab = ttk.TTkKodeTab(closable=True) |
|
|
|
close_requested_called = [] |
|
kodeTab.kodeTabCloseRequested.connect( |
|
lambda tw, i: close_requested_called.append((tw, i)) |
|
) |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
|
|
kodeTab.addTab(widget1, "Tab 1") |
|
kodeTab.addTab(widget2, "Tab 2") |
|
|
|
# Simulate close request |
|
kodeTab._lastKodeTabWidget._handleTabCloseRequested(0) |
|
|
|
assert len(close_requested_called) == 1 |
|
assert close_requested_called[0][1] == 0 |
|
|
|
# ============================================================================ |
|
# TTkKodeTab Remove/Close Tests |
|
# ============================================================================ |
|
|
|
def test_kodetab_remove_tab(): |
|
''' |
|
Test removing tabs from the internal tab widget. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
widget3 = ttk.TTkWidget() |
|
|
|
kodeTab.addTab(widget1, "Tab 1") |
|
kodeTab.addTab(widget2, "Tab 2") |
|
kodeTab.addTab(widget3, "Tab 3") |
|
|
|
assert kodeTab._lastKodeTabWidget.count() == 3 |
|
|
|
kodeTab._lastKodeTabWidget.removeTab(1) |
|
|
|
assert kodeTab._lastKodeTabWidget.count() == 2 |
|
assert kodeTab._lastKodeTabWidget.widget(0) == widget1 |
|
assert kodeTab._lastKodeTabWidget.widget(1) == widget3 |
|
|
|
def test_kodetab_remove_all_tabs(): |
|
''' |
|
Test removing all tabs triggers cleanup of empty widgets. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
|
|
kodeTab.addTab(widget1, "Tab 1") |
|
kodeTab.addTab(widget2, "Tab 2") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
internal_tab.removeTab(0) |
|
internal_tab.removeTab(0) |
|
|
|
# Should still have the base internal tab widget |
|
assert kodeTab.count() >= 0 |
|
|
|
def test_kodetab_kode_tab_closed(): |
|
''' |
|
Test _kodeTabClosed cleanup logic. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widget1 = ttk.TTkWidget() |
|
kodeTab.addTab(widget1, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Remove the tab |
|
internal_tab.removeTab(0) |
|
|
|
# Trigger cleanup |
|
internal_tab._kodeTabClosed() |
|
|
|
# Verify state |
|
assert isinstance(kodeTab._getFirstWidget(), _TTkKodeTab) |
|
|
|
# ============================================================================ |
|
# TTkKodeTab Drag and Drop Tests |
|
# ============================================================================ |
|
|
|
def test_kodetab_drag_enter_event(): |
|
''' |
|
Test drag enter event handling. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget = ttk.TTkWidget() |
|
kodeTab.addTab(widget, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Create a mock drag event |
|
evt = TTkDnDEvent(pos=(5,5), data=None) |
|
|
|
# Test drag enter |
|
result = internal_tab.dragEnterEvent(evt) |
|
assert result is True |
|
|
|
def test_kodetab_drag_leave_event(): |
|
''' |
|
Test drag leave event handling. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget = ttk.TTkWidget() |
|
kodeTab.addTab(widget, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
internal_tab._frameOverlay = (0, 0, 10, 10) |
|
|
|
evt = TTkDnDEvent(pos=(5,5), data=None) |
|
|
|
result = internal_tab.dragLeaveEvent(evt) |
|
assert result is True |
|
assert internal_tab._frameOverlay is None |
|
|
|
def test_kodetab_drop_event_new_tab_data(): |
|
''' |
|
Test dropping new tab data onto TTkKodeTab. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget1 = ttk.TTkWidget() |
|
kodeTab.addTab(widget1, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Create new tab drag data |
|
new_widget = ttk.TTkWidget() |
|
drag_data = _TTkNewTabWidgetDragData("New Tab", new_widget, data="test_data") |
|
|
|
# Create drop event in center (should add to existing tab bar) |
|
evt = TTkDnDEvent(pos=(50,25), data=drag_data) |
|
|
|
result = internal_tab.dropEvent(evt) |
|
|
|
# Verify drop was handled |
|
assert result in (True, False) # Depends on implementation details |
|
|
|
# def test_kodetab_drop_event_tab_widget_data(): |
|
# ''' |
|
# Test dropping tab from another widget. |
|
# ''' |
|
# kodeTab1 = ttk.TTkKodeTab() |
|
# kodeTab2 = ttk.TTkKodeTab() |
|
|
|
# widget1 = ttk.TTkWidget() |
|
# widget2 = ttk.TTkWidget() |
|
|
|
# kodeTab1.addTab(widget1, "Tab 1") |
|
# kodeTab2.addTab(widget2, "Tab 2") |
|
|
|
# internal_tab1 = kodeTab1._lastKodeTabWidget |
|
# internal_tab2 = kodeTab2._lastKodeTabWidget |
|
|
|
# # Get the tab button to drag |
|
# button = internal_tab2.tabButton(0) |
|
|
|
# # Create drag data |
|
# drag_data = _TTkTabWidgetDragData(button, internal_tab2) |
|
|
|
# # Drop in center |
|
# evt = TTkDnDEvent(pos=(50,25), data=drag_data) |
|
|
|
# result = internal_tab1.dropEvent(evt) |
|
|
|
# # Verify handling occurred |
|
# assert result in (True, False) |
|
|
|
def test_kodetab_drop_event_list_of_new_tabs(): |
|
''' |
|
Test dropping a list of new tabs. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget1 = ttk.TTkWidget() |
|
kodeTab.addTab(widget1, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Create list of new tab data |
|
new_widget1 = ttk.TTkWidget() |
|
new_widget2 = ttk.TTkWidget() |
|
|
|
drag_data_list = [ |
|
_TTkNewTabWidgetDragData("New Tab 1", new_widget1), |
|
_TTkNewTabWidgetDragData("New Tab 2", new_widget2) |
|
] |
|
|
|
# Drop in center |
|
evt = TTkDnDEvent(pos=(50,25), data=drag_data_list) |
|
|
|
result = internal_tab.dropEvent(evt) |
|
|
|
assert result in (True, False) |
|
|
|
def test_kodetab_drop_creates_horizontal_split(): |
|
''' |
|
Test that dropping on left/right zones creates horizontal splits. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget1 = ttk.TTkWidget() |
|
kodeTab.addTab(widget1, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Mock size for positioning |
|
internal_tab._size = (100, 50) |
|
|
|
new_widget = ttk.TTkWidget() |
|
drag_data = _TTkNewTabWidgetDragData("New Tab", new_widget) |
|
|
|
# Drop on left zone (x < w/4) |
|
evt = TTkDnDEvent(pos=(10,25), data=drag_data) |
|
|
|
initial_count = kodeTab.count() |
|
result = internal_tab.dropEvent(evt) |
|
|
|
# A split may have been created |
|
# Exact behavior depends on implementation |
|
assert kodeTab.count() >= initial_count |
|
|
|
def test_kodetab_drop_creates_vertical_split(): |
|
''' |
|
Test that dropping on top/bottom zones creates vertical splits. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget1 = ttk.TTkWidget() |
|
kodeTab.addTab(widget1, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Mock size |
|
internal_tab._size = (100, 50) |
|
|
|
new_widget = ttk.TTkWidget() |
|
drag_data = _TTkNewTabWidgetDragData("New Tab", new_widget) |
|
|
|
# Drop on top zone (y < h/4, adjusted for tab bar) |
|
evt = TTkDnDEvent(pos=(50,5), data=drag_data) |
|
|
|
initial_count = kodeTab.count() |
|
result = internal_tab.dropEvent(evt) |
|
|
|
assert kodeTab.count() >= initial_count |
|
|
|
def test_kodetab_set_drop_event_proxy(): |
|
''' |
|
Test setting drop event proxy propagates to internal widgets. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget = ttk.TTkWidget() |
|
kodeTab.addTab(widget, "Tab 1") |
|
|
|
proxy_called = [] |
|
|
|
def proxy(evt): |
|
proxy_called.append(evt) |
|
return True |
|
|
|
kodeTab.setDropEventProxy(proxy) |
|
|
|
# Verify proxy is set |
|
internal_tab = kodeTab._lastKodeTabWidget |
|
assert internal_tab._dropEventProxy == proxy |
|
|
|
# ============================================================================ |
|
# TTkKodeTab Complex Scenarios |
|
# ============================================================================ |
|
|
|
def test_kodetab_multiple_splits(): |
|
''' |
|
Test creating multiple splits through tab operations. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
|
|
kodeTab.addTab(widget1, "Tab 1") |
|
kodeTab.addTab(widget2, "Tab 2") |
|
|
|
# Count all tab items |
|
items_before = list(kodeTab.iterItems()) |
|
assert len(items_before) == 2 |
|
|
|
def test_kodetab_get_first_widget(): |
|
''' |
|
Test getting the first internal widget. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
first = kodeTab._getFirstWidget() |
|
assert isinstance(first, _TTkKodeTab) |
|
assert first._baseWidget == kodeTab |
|
|
|
def test_kodetab_has_menu(): |
|
''' |
|
Test checking if internal tab has menu. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget = ttk.TTkWidget() |
|
kodeTab.addTab(widget, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Initially should not have menu |
|
assert internal_tab._hasMenu() is False |
|
|
|
def test_kodetab_add_menu(): |
|
''' |
|
Test adding menu to TTkKodeTab. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget = ttk.TTkWidget() |
|
kodeTab.addTab(widget, "Tab 1") |
|
|
|
# Add a menu item |
|
menu_item = kodeTab.addMenu("File", data="file_data") |
|
|
|
assert menu_item is not None |
|
|
|
def test_kodetab_bar_type(): |
|
''' |
|
Test different bar types for TTkKodeTab. |
|
''' |
|
from TermTk.TTkWidgets.tabwidget import TTkBarType |
|
|
|
kodeTab1 = ttk.TTkKodeTab(barType=TTkBarType.DEFAULT_3) |
|
kodeTab2 = ttk.TTkKodeTab(barType=TTkBarType.NERD_1) |
|
|
|
assert kodeTab1._barType == TTkBarType.DEFAULT_3 |
|
assert kodeTab2._barType == TTkBarType.NERD_1 |
|
|
|
def test_kodetab_empty_after_all_removals(): |
|
''' |
|
Test that TTkKodeTab handles becoming empty gracefully. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widget1 = ttk.TTkWidget() |
|
widget2 = ttk.TTkWidget() |
|
|
|
kodeTab.addTab(widget1, "Tab 1") |
|
kodeTab.addTab(widget2, "Tab 2") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Remove all tabs |
|
internal_tab.removeTab(0) |
|
internal_tab.removeTab(0) |
|
|
|
# Trigger cleanup |
|
internal_tab._kodeTabClosed() |
|
|
|
# Should still be in valid state |
|
assert kodeTab._getFirstWidget() is not None |
|
|
|
def test_kodetab_paint_child_canvas(): |
|
''' |
|
Test that paintChildCanvas handles frame overlay correctly. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget = ttk.TTkWidget() |
|
kodeTab.addTab(widget, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Set frame overlay |
|
internal_tab._frameOverlay = (10, 10, 50, 30) |
|
|
|
# Call paintChildCanvas (should not raise) |
|
try: |
|
internal_tab.paintChildCanvas() |
|
assert True |
|
except Exception as e: |
|
assert False, f"paintChildCanvas raised exception: {e}" |
|
|
|
# Clear overlay |
|
internal_tab._frameOverlay = None |
|
internal_tab.paintChildCanvas() |
|
|
|
def test_kodetab_drop_invalid_data(): |
|
''' |
|
Test that dropping invalid data returns False. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
widget = ttk.TTkWidget() |
|
kodeTab.addTab(widget, "Tab 1") |
|
|
|
internal_tab = kodeTab._lastKodeTabWidget |
|
|
|
# Create event with invalid data |
|
evt = TTkDnDEvent(pos=(50,25), data="invalid_data") |
|
|
|
result = internal_tab.dropEvent(evt) |
|
|
|
# Should not accept invalid data |
|
assert result is False |
|
|
|
def test_kodetab_iter_items_after_operations(): |
|
''' |
|
Test that iterItems works correctly after add/remove operations. |
|
''' |
|
kodeTab = ttk.TTkKodeTab() |
|
|
|
widgets = [ttk.TTkWidget() for _ in range(5)] |
|
|
|
for i, widget in enumerate(widgets): |
|
kodeTab.addTab(widget, f"Tab {i}") |
|
|
|
items = list(kodeTab.iterItems()) |
|
assert len(items) == 5 |
|
|
|
# Remove some tabs |
|
kodeTab._lastKodeTabWidget.removeTab(2) |
|
kodeTab._lastKodeTabWidget.removeTab(1) |
|
|
|
items = list(kodeTab.iterItems()) |
|
assert len(items) == 3
|
|
|