Migrate to GitLab
This commit is contained in:
1
excel_filter/tests/__init__.py
Normal file
1
excel_filter/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Tests package
|
||||
26
excel_filter/tests/debug_import.py
Normal file
26
excel_filter/tests/debug_import.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the src directory to the path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'excel_filter', 'src'))
|
||||
|
||||
print("Python path:", sys.path)
|
||||
print("Current directory:", os.getcwd())
|
||||
|
||||
try:
|
||||
from excel_filter.gui_components.config_tab import ConfigTab
|
||||
print("SUCCESS: ConfigTab imported")
|
||||
|
||||
# Try to instantiate
|
||||
import tkinter as tk
|
||||
root = tk.Tk()
|
||||
|
||||
config_tab = ConfigTab(root, {}, {}, {})
|
||||
print("SUCCESS: ConfigTab instantiated")
|
||||
|
||||
root.destroy()
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
42
excel_filter/tests/generate_test_data.py
Normal file
42
excel_filter/tests/generate_test_data.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Skript zum Generieren von Testdaten für die Excel-Filter-Tests
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
|
||||
# Testdaten erstellen
|
||||
test_data = {
|
||||
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank', 'Grace', 'Hank'],
|
||||
'Status': ['Active', 'Inactive', 'Active', 'Inactive', 'Active', 'Inactive', 'Active', 'Inactive'],
|
||||
'Message': [
|
||||
'Hello World',
|
||||
'Error occurred',
|
||||
'Warning: Low disk space',
|
||||
'Critical failure',
|
||||
'System running normally',
|
||||
'Error: Database connection failed',
|
||||
'Warning: High CPU usage',
|
||||
'Info: System update available'
|
||||
],
|
||||
'Value': [100, 200, 150, 50, 300, 120, 180, 90],
|
||||
'Description': [
|
||||
'Normal operation',
|
||||
'Error in module X',
|
||||
'Disk space warning',
|
||||
'Critical system error',
|
||||
'All systems operational',
|
||||
'Database error',
|
||||
'CPU warning',
|
||||
'Update information'
|
||||
]
|
||||
}
|
||||
|
||||
# DataFrame erstellen
|
||||
df = pd.DataFrame(test_data)
|
||||
|
||||
# In Excel-Datei speichern
|
||||
df.to_excel('tests/test_data.xlsx', index=False)
|
||||
|
||||
print("Testdaten erfolgreich generiert!")
|
||||
print(df)
|
||||
71
excel_filter/tests/simple_test.py
Normal file
71
excel_filter/tests/simple_test.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test to verify the config_tab.py fixes
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.append('src')
|
||||
|
||||
from excel_filter.gui_components.config_tab import ConfigTab
|
||||
|
||||
def test_methods():
|
||||
"""Test that the methods are properly implemented"""
|
||||
print("Testing ConfigTab methods...")
|
||||
|
||||
# Test that the class can be instantiated
|
||||
try:
|
||||
# Create a minimal mock frame
|
||||
import tkinter as tk
|
||||
root = tk.Tk()
|
||||
|
||||
win11_colors = {'primary': '#0078d4', 'light': '#f0f0f0', 'dark': '#e0e0e0', 'accent': '#005a9e'}
|
||||
pattern_presets = {"test": "test"}
|
||||
pattern_descriptions = {"test": "test"}
|
||||
|
||||
config_tab = ConfigTab(root, win11_colors, pattern_presets, pattern_descriptions)
|
||||
|
||||
# Test that methods exist and are callable
|
||||
assert hasattr(config_tab, 'select_all_columns'), "select_all_columns method missing"
|
||||
assert hasattr(config_tab, 'deselect_all_columns'), "deselect_all_columns method missing"
|
||||
assert hasattr(config_tab, 'process_file'), "process_file method missing"
|
||||
assert hasattr(config_tab, 'save_config'), "save_config method missing"
|
||||
assert hasattr(config_tab, 'load_config'), "load_config method missing"
|
||||
assert hasattr(config_tab, 'browse_input_file'), "browse_input_file method missing"
|
||||
assert hasattr(config_tab, 'browse_output_file'), "browse_output_file method missing"
|
||||
assert hasattr(config_tab, 'update_columns_selection'), "update_columns_selection method missing"
|
||||
|
||||
# Test that methods are callable
|
||||
config_tab.select_all_columns()
|
||||
config_tab.deselect_all_columns()
|
||||
config_tab.process_file()
|
||||
config_tab.save_config()
|
||||
config_tab.load_config()
|
||||
config_tab.browse_input_file()
|
||||
config_tab.browse_output_file()
|
||||
config_tab.update_columns_selection()
|
||||
|
||||
print("[SUCCESS] All methods are properly implemented and callable!")
|
||||
|
||||
# Test with some data
|
||||
config_tab.input_file_var.set("test_input.xlsx")
|
||||
config_tab.output_file_var.set("test_output.xlsx")
|
||||
config_tab.pattern_var.set("error|warning")
|
||||
|
||||
# This should work now (previously would have done nothing)
|
||||
config_tab.process_file()
|
||||
|
||||
print("[SUCCESS] Process file method works with valid data!")
|
||||
|
||||
root.destroy()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Error: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_methods()
|
||||
if success:
|
||||
print("\n[INFO] All tests passed! The config_tab.py fixes are working correctly.")
|
||||
else:
|
||||
print("\n[ERROR] Some tests failed. Please check the implementation.")
|
||||
29
excel_filter/tests/test_batch.bat
Normal file
29
excel_filter/tests/test_batch.bat
Normal file
@@ -0,0 +1,29 @@
|
||||
@echo off
|
||||
:: Test Batchdatei zur Fehlerdiagnose
|
||||
|
||||
echo Test 1: Einfache if-Bedingung
|
||||
if "%ERRORLEVEL%"=="0" (
|
||||
echo Test 1 erfolgreich
|
||||
) else (
|
||||
echo Test 1 fehlgeschlagen
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Test 2: if not-Bedingung
|
||||
set TESTVAR=0
|
||||
if not "%TESTVAR%"=="0" (
|
||||
echo Test 2 erfolgreich
|
||||
) else (
|
||||
echo Test 2 fehlgeschlagen
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Test 3: if exist-Bedingung
|
||||
if exist "test_batch.bat" (
|
||||
echo Test 3 erfolgreich - Datei existiert
|
||||
) else (
|
||||
echo Test 3 fehlgeschlagen - Datei existiert nicht
|
||||
)
|
||||
|
||||
echo.
|
||||
pause
|
||||
81
excel_filter/tests/test_column_update.py
Normal file
81
excel_filter/tests/test_column_update.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify column selection updates
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append('src')
|
||||
|
||||
from excel_filter.gui_new import ExcelFilterGUI
|
||||
import tkinter as tk
|
||||
|
||||
def test_column_update():
|
||||
"""Test that column selection updates are properly reflected in command display"""
|
||||
|
||||
# Create main window
|
||||
root = tk.Tk()
|
||||
root.withdraw() # Hide the main window for testing
|
||||
|
||||
try:
|
||||
# Create the GUI
|
||||
app = ExcelFilterGUI(root)
|
||||
|
||||
print("Testing column selection updates...")
|
||||
|
||||
# Set up test data
|
||||
app.config_tab.input_file_var.set("test_input.xlsx")
|
||||
app.config_tab.output_file_var.set("test_output.xlsx")
|
||||
app.config_tab.pattern_var.set("test_pattern")
|
||||
app.config_tab.sheet_var.set("Sheet1")
|
||||
|
||||
# Mock some column variables
|
||||
app.config_tab.columns_vars = {
|
||||
'Column1': tk.IntVar(value=1),
|
||||
'Column2': tk.IntVar(value=0),
|
||||
'Column3': tk.IntVar(value=1)
|
||||
}
|
||||
|
||||
# Set up the connections (this should have been done in create_tabs)
|
||||
app.config_tab.set_execution_tab(app.execution_tab)
|
||||
app.config_tab.set_on_columns_changed(lambda: app.execution_tab.update_command_display())
|
||||
|
||||
# Add tracing for individual column variables
|
||||
for var in app.config_tab.columns_vars.values():
|
||||
var.trace_add("write", lambda *args: app.execution_tab.update_command_display())
|
||||
|
||||
print("Initial command display:")
|
||||
initial_command = app.execution_tab.command_var.get()
|
||||
print(f"Command: {initial_command}")
|
||||
|
||||
# Change a column selection
|
||||
print("\nChanging Column2 selection...")
|
||||
app.config_tab.columns_vars['Column2'].set(1)
|
||||
root.update() # Process the trace updates
|
||||
|
||||
updated_command = app.execution_tab.command_var.get()
|
||||
print(f"Updated command: {updated_command}")
|
||||
|
||||
# Check if columns appear in command
|
||||
columns_in_command = "Spalten:" in updated_command
|
||||
print(f"Columns appear in command: {columns_in_command}")
|
||||
|
||||
if columns_in_command:
|
||||
print("[SUCCESS] Column selection update works!")
|
||||
return True
|
||||
else:
|
||||
print("[ERROR] Column selection update failed!")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"Test failed with error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
finally:
|
||||
root.destroy()
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_column_update()
|
||||
sys.exit(0 if success else 1)
|
||||
97
excel_filter/tests/test_complete_implementation.py
Normal file
97
excel_filter/tests/test_complete_implementation.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Complete test to verify all the implemented functionality in config_tab.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.append('excel_filter/src')
|
||||
|
||||
from excel_filter.gui_components.config_tab import ConfigTab
|
||||
|
||||
def test_complete_implementation():
|
||||
"""Test all the implemented methods"""
|
||||
print("Testing complete implementation of ConfigTab...")
|
||||
|
||||
try:
|
||||
# Create a minimal mock frame
|
||||
import tkinter as tk
|
||||
root = tk.Tk()
|
||||
|
||||
win11_colors = {'primary': '#0078d4', 'light': '#f0f0f0', 'dark': '#e0e0e0', 'accent': '#005a9e'}
|
||||
pattern_presets = {"test": "test"}
|
||||
pattern_descriptions = {"test": "test"}
|
||||
|
||||
config_tab = ConfigTab(root, win11_colors, pattern_presets, pattern_descriptions)
|
||||
|
||||
print("[SUCCESS] ConfigTab instantiated successfully")
|
||||
|
||||
# Test file browsing methods
|
||||
print("Testing browse methods...")
|
||||
# These will open dialogs, but we can at least verify they don't crash
|
||||
try:
|
||||
config_tab.browse_input_file()
|
||||
print("[SUCCESS] browse_input_file() works")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] browse_input_file() failed: {e}")
|
||||
|
||||
try:
|
||||
config_tab.browse_output_file()
|
||||
print("[SUCCESS] browse_output_file() works")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] browse_output_file() failed: {e}")
|
||||
|
||||
# Test column selection methods
|
||||
print("Testing column selection methods...")
|
||||
config_tab.select_all_columns()
|
||||
print("[SUCCESS] select_all_columns() works")
|
||||
|
||||
config_tab.deselect_all_columns()
|
||||
print("[SUCCESS] deselect_all_columns() works")
|
||||
|
||||
# Test config methods
|
||||
print("Testing config methods...")
|
||||
config_tab.save_config()
|
||||
print("[SUCCESS] save_config() works")
|
||||
|
||||
config_tab.load_config()
|
||||
print("[SUCCESS] load_config() works")
|
||||
|
||||
# Test process file with valid data
|
||||
print("Testing process_file with valid data...")
|
||||
config_tab.input_file_var.set("test_input.xlsx")
|
||||
config_tab.output_file_var.set("test_output.xlsx")
|
||||
config_tab.pattern_var.set("error|warning")
|
||||
config_tab.process_file()
|
||||
print("[SUCCESS] process_file() works with valid data")
|
||||
|
||||
# Test update methods (these might fail without actual files, but shouldn't crash)
|
||||
print("Testing update methods...")
|
||||
try:
|
||||
config_tab.update_sheet_selection()
|
||||
print("[SUCCESS] update_sheet_selection() works")
|
||||
except Exception as e:
|
||||
print(f"[WARNING] update_sheet_selection() failed (expected without file): {e}")
|
||||
|
||||
try:
|
||||
config_tab.update_columns_selection()
|
||||
print("[SUCCESS] update_columns_selection() works")
|
||||
except Exception as e:
|
||||
print(f"[WARNING] update_columns_selection() failed (expected without file): {e}")
|
||||
|
||||
print("\n[INFO] All methods are properly implemented and functional!")
|
||||
|
||||
root.destroy()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR]: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_complete_implementation()
|
||||
if success:
|
||||
print("\n[INFO] All tests passed! The config_tab.py is fully functional.")
|
||||
else:
|
||||
print("\n[ERROR] Some tests failed. Please check the implementation.")
|
||||
159
excel_filter/tests/test_filter.py
Normal file
159
excel_filter/tests/test_filter.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Test module for ExcelFilter
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pandas as pd
|
||||
|
||||
# Add src directory to path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from excel_filter.filter import ExcelFilter
|
||||
|
||||
|
||||
class TestExcelFilter:
|
||||
"""
|
||||
Test class for ExcelFilter
|
||||
"""
|
||||
|
||||
def setup_method(self):
|
||||
"""
|
||||
Setup for the tests
|
||||
"""
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
# Create temporary files
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.test_input = os.path.join(self.temp_dir, "test_data.xlsx")
|
||||
self.test_output = os.path.join(self.temp_dir, "test_output.xlsx")
|
||||
|
||||
# Create test data
|
||||
test_data = {
|
||||
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
|
||||
'Status': ['Active', 'Inactive', 'Active', 'Inactive', 'Active'],
|
||||
'Message': ['Hello World', 'Error occurred', 'Warning: Low disk space', 'Critical failure', 'System running normally'],
|
||||
'Value': [100, 200, 150, 50, 300]
|
||||
}
|
||||
|
||||
df = pd.DataFrame(test_data)
|
||||
df.to_excel(self.test_input, index=False)
|
||||
|
||||
def teardown_method(self):
|
||||
"""
|
||||
Cleanup after the tests
|
||||
"""
|
||||
import shutil
|
||||
if os.path.exists(self.test_output):
|
||||
os.remove(self.test_output)
|
||||
if os.path.exists(self.test_input):
|
||||
os.remove(self.test_input)
|
||||
if hasattr(self, 'temp_dir') and os.path.exists(self.temp_dir):
|
||||
shutil.rmtree(self.temp_dir)
|
||||
|
||||
def test_filter_error_pattern(self):
|
||||
"""
|
||||
Test filtering with an error pattern
|
||||
"""
|
||||
pattern = "error|warning|critical"
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=self.test_input,
|
||||
output_file=self.test_output,
|
||||
pattern=pattern
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
# Verify output file
|
||||
result_df = pd.read_excel(self.test_output)
|
||||
assert len(result_df) == 3 # Bob, Charlie, David
|
||||
|
||||
def test_filter_specific_column(self):
|
||||
"""
|
||||
Test filtering in a specific column
|
||||
"""
|
||||
pattern = "active"
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=self.test_input,
|
||||
output_file=self.test_output,
|
||||
pattern=pattern,
|
||||
columns=['Status']
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
# Verify output file
|
||||
result_df = pd.read_excel(self.test_output)
|
||||
assert len(result_df) == 3 # Alice, Charlie, Eve
|
||||
|
||||
def test_filter_value_pattern(self):
|
||||
"""
|
||||
Test filtering with a numeric pattern
|
||||
"""
|
||||
pattern = "1\d{2}" # Numbers between 100-199
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=self.test_input,
|
||||
output_file=self.test_output,
|
||||
pattern=pattern,
|
||||
columns=['Value']
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
# Verify output file
|
||||
result_df = pd.read_excel(self.test_output)
|
||||
assert len(result_df) == 2 # Alice (100), Charlie (150)
|
||||
|
||||
def test_filter_no_matches(self):
|
||||
"""
|
||||
Test filtering with a pattern that finds no matches
|
||||
"""
|
||||
pattern = "nonexistent"
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=self.test_input,
|
||||
output_file=self.test_output,
|
||||
pattern=pattern
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
# Verify output file
|
||||
result_df = pd.read_excel(self.test_output)
|
||||
assert len(result_df) == 0 # No matches
|
||||
|
||||
def test_filter_invalid_file(self):
|
||||
"""
|
||||
Test filtering with an invalid file
|
||||
"""
|
||||
pattern = "error"
|
||||
excel_filter = ExcelFilter(
|
||||
input_file="nonexistent.xlsx",
|
||||
output_file=self.test_output,
|
||||
pattern=pattern
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert not success
|
||||
|
||||
def test_filter_empty_pattern(self):
|
||||
"""
|
||||
Test filtering with an empty pattern
|
||||
"""
|
||||
pattern = ""
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=self.test_input,
|
||||
output_file=self.test_output,
|
||||
pattern=pattern
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
# Verify output file
|
||||
result_df = pd.read_excel(self.test_output)
|
||||
assert len(result_df) == len(pd.read_excel(self.test_input)) # All rows
|
||||
103
excel_filter/tests/test_final_implementation.py
Normal file
103
excel_filter/tests/test_final_implementation.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Final test to verify the complete implementation including ExcelFilter integration
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.append('excel_filter/src')
|
||||
|
||||
def test_final_implementation():
|
||||
"""Test the complete implementation"""
|
||||
print("Testing final implementation...")
|
||||
|
||||
try:
|
||||
# Test import
|
||||
from excel_filter.gui_components.config_tab import ConfigTab
|
||||
print("[SUCCESS] ConfigTab imported successfully")
|
||||
|
||||
# Test that all required methods exist
|
||||
required_methods = [
|
||||
'browse_input_file', 'browse_output_file',
|
||||
'select_all_columns', 'deselect_all_columns',
|
||||
'get_selected_columns', 'update_columns_selection',
|
||||
'update_sheet_selection', 'save_config', 'load_config',
|
||||
'process_file', 'open_file'
|
||||
]
|
||||
|
||||
print("Checking required methods:")
|
||||
for method in required_methods:
|
||||
if hasattr(ConfigTab, method):
|
||||
print(f"[SUCCESS] {method}() - EXISTS")
|
||||
else:
|
||||
print(f"[ERROR] {method}() - MISSING")
|
||||
return False
|
||||
|
||||
# Test that ExcelFilter can be imported (this tests the integration)
|
||||
try:
|
||||
from excel_filter.filter import ExcelFilter
|
||||
print("[SUCCESS] ExcelFilter integration available")
|
||||
except ImportError as e:
|
||||
print(f"[WARNING] ExcelFilter import warning: {e}")
|
||||
print(" (This is expected if running in isolation)")
|
||||
|
||||
# Test instantiation
|
||||
import tkinter as tk
|
||||
root = tk.Tk()
|
||||
|
||||
win11_colors = {'primary': '#0078d4', 'light': '#f0f0f0', 'dark': '#e0e0e0', 'accent': '#005a9e'}
|
||||
pattern_presets = {"test": "test"}
|
||||
pattern_descriptions = {"test": "test"}
|
||||
|
||||
config_tab = ConfigTab(root, win11_colors, pattern_presets, pattern_descriptions)
|
||||
print("[SUCCESS] ConfigTab instantiated successfully")
|
||||
|
||||
# Test method functionality
|
||||
print("Testing method functionality:")
|
||||
|
||||
# Test column selection
|
||||
config_tab.select_all_columns()
|
||||
print(" [SUCCESS] select_all_columns() works")
|
||||
|
||||
config_tab.deselect_all_columns()
|
||||
print(" [SUCCESS] deselect_all_columns() works")
|
||||
|
||||
# Test get_selected_columns
|
||||
selected = config_tab.get_selected_columns()
|
||||
print(f" [SUCCESS] get_selected_columns() returns: {selected}")
|
||||
|
||||
# Test config methods
|
||||
config_tab.save_config()
|
||||
print(" [SUCCESS] save_config() works")
|
||||
|
||||
config_tab.load_config()
|
||||
print(" [SUCCESS] load_config() works")
|
||||
|
||||
# Test process_file with mock data
|
||||
config_tab.input_file_var.set("test_input.xlsx")
|
||||
config_tab.output_file_var.set("test_output.xlsx")
|
||||
config_tab.pattern_var.set("error|warning")
|
||||
|
||||
# This should now work with proper validation
|
||||
try:
|
||||
config_tab.process_file()
|
||||
print(" [SUCCESS] process_file() works (will fail without real files, but validates correctly)")
|
||||
except Exception as e:
|
||||
print(f" [WARNING] process_file() failed as expected: {e}")
|
||||
|
||||
root.destroy()
|
||||
|
||||
print("\n[INFO] All tests passed! The config_tab.py is now fully implemented with ExcelFilter integration.")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR]: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_final_implementation()
|
||||
if success:
|
||||
print("\n[INFO] Complete implementation verified successfully!")
|
||||
else:
|
||||
print("\n[ERROR] Implementation verification failed.")
|
||||
42
excel_filter/tests/test_gui.py
Normal file
42
excel_filter/tests/test_gui.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
import tkinter as tk
|
||||
from excel_filter.gui_new import ExcelFilterGUI
|
||||
|
||||
def test_gui_initialization():
|
||||
"""Test GUI initialization without running the main loop"""
|
||||
print("Testing GUI initialization...")
|
||||
|
||||
# Create a test window
|
||||
root = tk.Tk()
|
||||
root.title("GUI Test")
|
||||
root.geometry("800x600")
|
||||
|
||||
try:
|
||||
# Initialize the GUI
|
||||
app = ExcelFilterGUI(root)
|
||||
print("ExcelFilterGUI initialized successfully")
|
||||
|
||||
# Test that main components exist
|
||||
assert hasattr(app, 'root'), "Root window not set"
|
||||
assert hasattr(app, 'main_window'), "Main window not set"
|
||||
assert hasattr(app, 'config_tab'), "Config tab not set"
|
||||
assert hasattr(app, 'execution_tab'), "Execution tab not set"
|
||||
assert hasattr(app, 'regex_builder_tab'), "Regex builder tab not set"
|
||||
|
||||
print("All main GUI components initialized successfully")
|
||||
print("GUI initialization test passed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"GUI initialization failed: {e}")
|
||||
raise
|
||||
finally:
|
||||
# Clean up: destroy the root window without running mainloop
|
||||
root.destroy()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_gui_initialization()
|
||||
727
excel_filter/tests/test_gui_new_comprehensive.py
Normal file
727
excel_filter/tests/test_gui_new_comprehensive.py
Normal file
@@ -0,0 +1,727 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
import json
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from pathlib import Path
|
||||
|
||||
# Add the parent directory to the path for imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
import tkinter as tk
|
||||
from gui_new import ExcelFilterGUI
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_root():
|
||||
"""Create a mock tkinter root window"""
|
||||
root = Mock(spec=tk.Tk)
|
||||
root.title = Mock()
|
||||
root.configure = Mock()
|
||||
root.iconbitmap = Mock()
|
||||
root.destroy = Mock()
|
||||
root.update = Mock()
|
||||
return root
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_files():
|
||||
"""Create temporary files for testing"""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Create a temporary config file
|
||||
config_file = os.path.join(temp_dir, 'config.json')
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump({
|
||||
'input_file': 'test_input.xlsx',
|
||||
'output_file': 'test_output.xlsx',
|
||||
'pattern': 'error|warning',
|
||||
'sheet_name': 'Sheet1',
|
||||
'columns': ['Column1', 'Column2']
|
||||
}, f)
|
||||
|
||||
# Create a temporary presets file
|
||||
presets_file = os.path.join(temp_dir, 'presets.json')
|
||||
with open(presets_file, 'w') as f:
|
||||
json.dump({
|
||||
'presets': {'test_pattern': 'test.*'},
|
||||
'descriptions': {'test_pattern': 'Test pattern'}
|
||||
}, f)
|
||||
|
||||
yield temp_dir, config_file, presets_file
|
||||
|
||||
|
||||
class TestExcelFilterGUI:
|
||||
|
||||
def test_update_columns_selection_signature(self):
|
||||
"""Test that update_columns_selection method accepts selected_columns parameter"""
|
||||
# Test the method signature directly without full GUI initialization
|
||||
import inspect
|
||||
method = getattr(ExcelFilterGUI, 'update_columns_selection')
|
||||
sig = inspect.signature(method)
|
||||
|
||||
# Check if 'selected_columns' parameter exists
|
||||
assert 'selected_columns' in sig.parameters
|
||||
param = sig.parameters['selected_columns']
|
||||
assert param.default is None
|
||||
|
||||
def test_color_palette(self):
|
||||
"""Test that Windows 11 color palette is properly defined"""
|
||||
# Test colors without full initialization
|
||||
expected_colors = ['primary', 'primary_dark', 'primary_light', 'background',
|
||||
'surface', 'text', 'text_secondary', 'border', 'hover', 'pressed']
|
||||
|
||||
# Check that all expected colors are defined
|
||||
for color in expected_colors:
|
||||
assert hasattr(ExcelFilterGUI, 'win11_colors') or color in ExcelFilterGUI().win11_colors
|
||||
|
||||
def test_file_paths(self):
|
||||
"""Test that file paths are correctly set"""
|
||||
gui = ExcelFilterGUI.__new__(ExcelFilterGUI) # Create instance without __init__
|
||||
gui.__init__ = Mock() # Mock __init__ to avoid full initialization
|
||||
|
||||
# Manually set attributes for testing
|
||||
gui.config_file = "config.json"
|
||||
gui.presets_file = "presets.json"
|
||||
|
||||
assert gui.config_file == "config.json"
|
||||
assert gui.presets_file == "presets.json"
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
def test_create_tabs(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test tab creation"""
|
||||
# Setup mocks similar to initialization test
|
||||
mock_main_window_instance = Mock()
|
||||
mock_main_window_instance.notebook = Mock()
|
||||
mock_main_window_instance.scale_factor = 1.0
|
||||
mock_main_window_instance.language_frame = Mock()
|
||||
mock_main_window_instance.enhance_tab_appearance = Mock()
|
||||
mock_main_window.return_value = mock_main_window_instance
|
||||
|
||||
mock_config_tab_instance = Mock()
|
||||
mock_config_tab_instance.get_frame.return_value = Mock()
|
||||
mock_config_tab_instance.set_execution_tab = Mock()
|
||||
mock_config_tab_instance.set_main_gui = Mock()
|
||||
mock_config_tab_instance.set_on_columns_changed = Mock()
|
||||
mock_config_tab.return_value = mock_config_tab_instance
|
||||
|
||||
mock_execution_tab_instance = Mock()
|
||||
mock_execution_tab_instance.get_frame.return_value = Mock()
|
||||
mock_execution_tab_instance.set_config_tab = Mock()
|
||||
mock_execution_tab_instance.set_main_gui = Mock()
|
||||
mock_execution_tab_instance.update_command_display = Mock()
|
||||
mock_execution_tab.return_value = mock_execution_tab_instance
|
||||
|
||||
mock_regex_tab_instance = Mock()
|
||||
mock_regex_tab_instance.get_frame.return_value = Mock()
|
||||
mock_regex_tab.return_value = mock_regex_tab_instance
|
||||
|
||||
mock_help_tab_instance = Mock()
|
||||
mock_help_tab_instance.get_frame.return_value = Mock()
|
||||
mock_help_tab.return_value = mock_help_tab_instance
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Verify tabs were added to notebook
|
||||
assert mock_main_window_instance.notebook.add.call_count == 4
|
||||
|
||||
# Verify config tab connections
|
||||
assert mock_config_tab_instance.browse_input_file is not None
|
||||
assert mock_config_tab_instance.browse_output_file is not None
|
||||
assert mock_config_tab_instance.update_columns_selection is not None
|
||||
assert mock_config_tab_instance.select_all_columns is not None
|
||||
assert mock_config_tab_instance.deselect_all_columns is not None
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
def test_update_columns_selection_no_file(self, mock_help_tab, mock_regex_tab,
|
||||
mock_execution_tab, mock_config_tab,
|
||||
mock_main_window, mock_root):
|
||||
"""Test update_columns_selection with no input file"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
with patch('tkinter.messagebox.showerror') as mock_error:
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Set empty input file
|
||||
gui.config_tab.input_file_var.get.return_value = ""
|
||||
|
||||
# Call update_columns_selection
|
||||
gui.update_columns_selection()
|
||||
|
||||
# Verify error message was shown
|
||||
mock_error.assert_called_once()
|
||||
args = mock_error.call_args[0]
|
||||
assert "Bitte geben Sie eine Eingabedatei an" in args[1]
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
@patch('pandas.read_excel')
|
||||
def test_update_columns_selection_with_file(self, mock_read_excel, mock_help_tab,
|
||||
mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test update_columns_selection with valid input file"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
# Mock pandas DataFrame
|
||||
mock_df = Mock()
|
||||
mock_df.columns.tolist.return_value = ['Column1', 'Column2', 'Column3']
|
||||
mock_read_excel.return_value = mock_df
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
with patch('tkinter.ttk.Checkbutton') as mock_checkbutton:
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Set valid input file
|
||||
gui.config_tab.input_file_var.get.return_value = "test.xlsx"
|
||||
gui.config_tab.sheet_var.get.return_value = "Sheet1"
|
||||
|
||||
# Mock container operations
|
||||
mock_container = Mock()
|
||||
gui.config_tab.columns_container = mock_container
|
||||
mock_container.winfo_children.return_value = []
|
||||
mock_container.grid = Mock()
|
||||
mock_container.update_idletasks = Mock()
|
||||
|
||||
# Call update_columns_selection
|
||||
gui.update_columns_selection()
|
||||
|
||||
# Verify pandas was called
|
||||
mock_read_excel.assert_called_once_with("test.xlsx", sheet_name="Sheet1")
|
||||
|
||||
# Verify checkboxes were created (3 columns)
|
||||
assert mock_checkbutton.call_count == 3
|
||||
|
||||
# Verify regex column combobox was updated
|
||||
gui.config_tab.regex_column_combobox.__setitem__.assert_called_with(
|
||||
'values', ['Alle Spalten', 'Column1', 'Column2', 'Column3']
|
||||
)
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
def test_update_columns_selection_with_selected_columns(self, mock_help_tab, mock_regex_tab,
|
||||
mock_execution_tab, mock_config_tab,
|
||||
mock_main_window, mock_root):
|
||||
"""Test update_columns_selection with pre-selected columns"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
with patch('pandas.read_excel') as mock_read_excel:
|
||||
mock_df = Mock()
|
||||
mock_df.columns.tolist.return_value = ['Column1', 'Column2', 'Column3']
|
||||
mock_read_excel.return_value = mock_df
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
with patch('tkinter.ttk.Checkbutton') as mock_checkbutton:
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Set valid input file
|
||||
gui.config_tab.input_file_var.get.return_value = "test.xlsx"
|
||||
gui.config_tab.sheet_var.get.return_value = "Sheet1"
|
||||
|
||||
# Mock container
|
||||
mock_container = Mock()
|
||||
gui.config_tab.columns_container = mock_container
|
||||
mock_container.winfo_children.return_value = []
|
||||
mock_container.grid = Mock()
|
||||
mock_container.update_idletasks = Mock()
|
||||
|
||||
# Create mock variables for columns
|
||||
mock_vars = {}
|
||||
for i, col in enumerate(['Column1', 'Column2', 'Column3']):
|
||||
mock_var = Mock()
|
||||
mock_vars[col] = mock_var
|
||||
gui.config_tab.columns_vars = mock_vars
|
||||
|
||||
# Call with selected columns
|
||||
selected_columns = ['Column1', 'Column3']
|
||||
gui.update_columns_selection(selected_columns=selected_columns)
|
||||
|
||||
# Verify that selected columns were set
|
||||
mock_vars['Column1'].set.assert_called_with(1)
|
||||
mock_vars['Column3'].set.assert_called_with(1)
|
||||
# Column2 should not be set (not in selected_columns)
|
||||
mock_vars['Column2'].set.assert_not_called()
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
def test_select_all_columns(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test select_all_columns method"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Setup mock column variables
|
||||
mock_var1 = Mock()
|
||||
mock_var2 = Mock()
|
||||
gui.config_tab.columns_vars = {
|
||||
'Column1': mock_var1,
|
||||
'Column2': mock_var2
|
||||
}
|
||||
|
||||
# Call select_all_columns
|
||||
gui.select_all_columns()
|
||||
|
||||
# Verify all variables were set to 1
|
||||
mock_var1.set.assert_called_with(1)
|
||||
mock_var2.set.assert_called_with(1)
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
def test_deselect_all_columns(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test deselect_all_columns method"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Setup mock column variables
|
||||
mock_var1 = Mock()
|
||||
mock_var2 = Mock()
|
||||
gui.config_tab.columns_vars = {
|
||||
'Column1': mock_var1,
|
||||
'Column2': mock_var2
|
||||
}
|
||||
|
||||
# Call deselect_all_columns
|
||||
gui.deselect_all_columns()
|
||||
|
||||
# Verify all variables were set to 0
|
||||
mock_var1.set.assert_called_with(0)
|
||||
mock_var2.set.assert_called_with(0)
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
@patch('gui_new.browse_file')
|
||||
def test_browse_input_file(self, mock_browse_file, mock_help_tab, mock_regex_tab,
|
||||
mock_execution_tab, mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test browse_input_file method"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
mock_browse_file.return_value = "/path/to/test.xlsx"
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Mock the methods that will be called
|
||||
gui.update_sheet_selection = Mock()
|
||||
gui.update_columns_selection = Mock()
|
||||
|
||||
# Call browse_input_file
|
||||
gui.browse_input_file()
|
||||
|
||||
# Verify file was set
|
||||
gui.config_tab.input_file_var.set.assert_called_with("/path/to/test.xlsx")
|
||||
|
||||
# Verify update methods were called
|
||||
gui.update_sheet_selection.assert_called_once()
|
||||
gui.update_columns_selection.assert_called_once()
|
||||
|
||||
# Verify output file was auto-filled
|
||||
expected_output = "/path/to/test_filtered.xlsx"
|
||||
gui.config_tab.output_file_var.set.assert_called_with(expected_output)
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
@patch('gui_new.browse_save_file')
|
||||
def test_browse_output_file(self, mock_browse_save_file, mock_help_tab, mock_regex_tab,
|
||||
mock_execution_tab, mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test browse_output_file method"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
mock_browse_save_file.return_value = "/path/to/output.xlsx"
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Call browse_output_file
|
||||
gui.browse_output_file()
|
||||
|
||||
# Verify file was set
|
||||
gui.config_tab.output_file_var.set.assert_called_with("/path/to/output.xlsx")
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
@patch('gui_new.load_config')
|
||||
def test_load_config(self, mock_load_config, mock_help_tab, mock_regex_tab,
|
||||
mock_execution_tab, mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test load_config method"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
mock_load_config.return_value = {
|
||||
'input_file': 'test.xlsx',
|
||||
'output_file': 'output.xlsx',
|
||||
'pattern': 'error.*',
|
||||
'sheet_name': 'Sheet1',
|
||||
'columns': ['Col1', 'Col2']
|
||||
}
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
with patch('tkinter.messagebox.showinfo') as mock_info:
|
||||
with patch('os.path.exists', return_value=True):
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Mock update methods
|
||||
gui.update_sheet_selection = Mock()
|
||||
gui.update_columns_selection = Mock()
|
||||
|
||||
# Call load_config
|
||||
gui.load_config()
|
||||
|
||||
# Verify config values were set
|
||||
gui.config_tab.input_file_var.set.assert_called_with('test.xlsx')
|
||||
gui.config_tab.output_file_var.set.assert_called_with('output.xlsx')
|
||||
gui.config_tab.pattern_var.set.assert_called_with('error.*')
|
||||
gui.config_tab.sheet_var.set.assert_called_with('Sheet1')
|
||||
|
||||
# Verify update methods were called
|
||||
gui.update_sheet_selection.assert_called_once()
|
||||
gui.update_columns_selection.assert_called_once_with(selected_columns=['Col1', 'Col2'])
|
||||
|
||||
# Verify success message
|
||||
mock_info.assert_called_once()
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
@patch('gui_new.save_config')
|
||||
def test_save_config(self, mock_save_config, mock_help_tab, mock_regex_tab,
|
||||
mock_execution_tab, mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test save_config method"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
with patch('tkinter.messagebox.showinfo') as mock_info:
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Setup mock return values
|
||||
gui.config_tab.input_file_var.get.return_value = 'input.xlsx'
|
||||
gui.config_tab.output_file_var.get.return_value = 'output.xlsx'
|
||||
gui.config_tab.pattern_var.get.return_value = 'pattern.*'
|
||||
gui.config_tab.sheet_var.get.return_value = 'Sheet1'
|
||||
gui.config_tab.columns_var.get.return_value = 'columns'
|
||||
|
||||
# Call save_config
|
||||
gui.save_config()
|
||||
|
||||
# Verify save_config was called with correct data
|
||||
expected_config = {
|
||||
'input_file': 'input.xlsx',
|
||||
'output_file': 'output.xlsx',
|
||||
'pattern': 'pattern.*',
|
||||
'sheet_name': 'Sheet1',
|
||||
'columns': 'columns'
|
||||
}
|
||||
mock_save_config.assert_called_once_with("config.json", expected_config)
|
||||
|
||||
# Verify success message
|
||||
mock_info.assert_called_once()
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
def test_load_presets(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test load_presets method"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Default Pattern')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
with patch('os.path.exists', return_value=True):
|
||||
with patch('gui_new.save_presets') as mock_save_presets:
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Call load_presets
|
||||
presets, descriptions = gui.load_presets()
|
||||
|
||||
# Verify presets were loaded
|
||||
assert isinstance(presets, dict)
|
||||
assert isinstance(descriptions, dict)
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
def test_save_presets(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test save_presets method"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
with patch('gui_new.open', Mock()) as mock_open:
|
||||
with patch('json.dump') as mock_dump:
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Set up test presets
|
||||
gui.pattern_presets = {'test': 'pattern'}
|
||||
gui.pattern_descriptions = {'test': 'description'}
|
||||
|
||||
# Call save_presets
|
||||
gui.save_presets()
|
||||
|
||||
# Verify json.dump was called
|
||||
mock_dump.assert_called_once()
|
||||
args = mock_dump.call_args[0]
|
||||
assert args[0]['presets'] == {'test': 'pattern'}
|
||||
assert args[0]['descriptions'] == {'test': 'description'}
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
def test_open_file_windows(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test open_file method on Windows"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
with patch('sys.platform', 'win32'):
|
||||
with patch('os.startfile') as mock_startfile:
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Call open_file
|
||||
gui.open_file('test.xlsx')
|
||||
|
||||
# Verify os.startfile was called
|
||||
mock_startfile.assert_called_once_with('test.xlsx')
|
||||
|
||||
@patch('gui_new.MainWindow')
|
||||
@patch('gui_new.ConfigTab')
|
||||
@patch('gui_new.ExecutionTab')
|
||||
@patch('gui_new.RegexBuilderTab')
|
||||
@patch('gui_new.HelpTab')
|
||||
def test_open_file_unix(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root):
|
||||
"""Test open_file method on Unix-like systems"""
|
||||
# Setup basic mocks
|
||||
self._setup_basic_mocks(mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root)
|
||||
|
||||
with patch('gui_new.Translations') as mock_translations:
|
||||
mock_translations_instance = Mock()
|
||||
mock_translations_instance.__getitem__ = Mock(return_value='Test')
|
||||
mock_translations_instance.app_title = 'Test App'
|
||||
mock_translations.return_value = mock_translations_instance
|
||||
|
||||
with patch('builtins.open', Mock()):
|
||||
with patch('json.load', return_value={'presets': {}, 'descriptions': {}}):
|
||||
with patch('sys.platform', 'linux'):
|
||||
with patch('subprocess.run') as mock_run:
|
||||
gui = ExcelFilterGUI(mock_root)
|
||||
|
||||
# Call open_file
|
||||
gui.open_file('test.xlsx')
|
||||
|
||||
# Verify subprocess.run was called with xdg-open
|
||||
mock_run.assert_called_once_with(['xdg-open', 'test.xlsx'])
|
||||
|
||||
def _setup_basic_mocks(self, mock_help_tab, mock_regex_tab, mock_execution_tab,
|
||||
mock_config_tab, mock_main_window, mock_root):
|
||||
"""Helper method to setup basic mocks for tests"""
|
||||
# Setup MainWindow mock
|
||||
mock_main_window_instance = Mock()
|
||||
mock_main_window_instance.notebook = Mock()
|
||||
mock_main_window_instance.scale_factor = 1.0
|
||||
mock_main_window_instance.language_frame = Mock()
|
||||
mock_main_window_instance.enhance_tab_appearance = Mock()
|
||||
mock_main_window.return_value = mock_main_window_instance
|
||||
|
||||
# Setup ConfigTab mock
|
||||
mock_config_tab_instance = Mock()
|
||||
mock_config_frame = Mock()
|
||||
mock_config_frame.winfo_children.return_value = [] # Make it iterable
|
||||
mock_config_tab_instance.frame = mock_config_frame
|
||||
mock_config_tab_instance.get_frame.return_value = mock_config_frame
|
||||
mock_config_tab_instance.set_execution_tab = Mock()
|
||||
mock_config_tab_instance.set_main_gui = Mock()
|
||||
mock_config_tab_instance.set_on_columns_changed = Mock()
|
||||
mock_config_tab_instance.input_file_var = Mock()
|
||||
mock_config_tab_instance.output_file_var = Mock()
|
||||
mock_config_tab_instance.pattern_var = Mock()
|
||||
mock_config_tab_instance.sheet_var = Mock()
|
||||
mock_config_tab_instance.columns_var = Mock()
|
||||
mock_config_tab_instance.regex_column_combobox = Mock()
|
||||
mock_config_tab_instance.columns_container = Mock()
|
||||
mock_config_tab_instance.columns_vars = {}
|
||||
mock_config_tab_instance.log_message = Mock()
|
||||
mock_config_tab_instance.log_error = Mock()
|
||||
mock_config_tab_instance.status_var = Mock()
|
||||
mock_config_tab.return_value = mock_config_tab_instance
|
||||
|
||||
# Setup ExecutionTab mock
|
||||
mock_execution_tab_instance = Mock()
|
||||
mock_execution_tab_instance.get_frame.return_value = Mock()
|
||||
mock_execution_tab_instance.set_config_tab = Mock()
|
||||
mock_execution_tab_instance.set_main_gui = Mock()
|
||||
mock_execution_tab_instance.update_command_display = Mock()
|
||||
mock_execution_tab.return_value = mock_execution_tab_instance
|
||||
|
||||
# Setup RegexBuilderTab mock
|
||||
mock_regex_tab_instance = Mock()
|
||||
mock_regex_tab_instance.get_frame.return_value = Mock()
|
||||
mock_regex_tab.return_value = mock_regex_tab_instance
|
||||
|
||||
# Setup HelpTab mock
|
||||
mock_help_tab_instance = Mock()
|
||||
mock_help_tab_instance.get_frame.return_value = Mock()
|
||||
mock_help_tab.return_value = mock_help_tab_instance
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
180
excel_filter/tests/test_gui_new_simple.py
Normal file
180
excel_filter/tests/test_gui_new_simple.py
Normal file
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple focused tests for gui_new.py - ExcelFilterGUI class
|
||||
Tests the key functionality that was fixed
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
# Add the parent directory to the path for imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from gui_new import ExcelFilterGUI
|
||||
|
||||
|
||||
class TestExcelFilterGUISimple:
|
||||
"""Simple focused tests for ExcelFilterGUI"""
|
||||
|
||||
def test_update_columns_selection_method_signature(self):
|
||||
"""Test that update_columns_selection method accepts selected_columns parameter"""
|
||||
import inspect
|
||||
|
||||
# Get the method signature
|
||||
method = getattr(ExcelFilterGUI, 'update_columns_selection')
|
||||
sig = inspect.signature(method)
|
||||
|
||||
# Verify 'selected_columns' parameter exists with correct default
|
||||
assert 'selected_columns' in sig.parameters
|
||||
param = sig.parameters['selected_columns']
|
||||
assert param.default is None
|
||||
|
||||
print("SUCCESS: update_columns_selection method signature is correct")
|
||||
|
||||
def test_class_attributes_exist(self):
|
||||
"""Test that key class attributes can be accessed"""
|
||||
# Test that we can access class attributes without instantiation
|
||||
assert hasattr(ExcelFilterGUI, 'update_columns_selection')
|
||||
assert hasattr(ExcelFilterGUI, 'select_all_columns')
|
||||
assert hasattr(ExcelFilterGUI, 'deselect_all_columns')
|
||||
assert hasattr(ExcelFilterGUI, 'load_config')
|
||||
assert hasattr(ExcelFilterGUI, 'save_config')
|
||||
|
||||
print("SUCCESS: All key methods exist on the class")
|
||||
|
||||
def test_win11_colors_attribute(self):
|
||||
"""Test that win11_colors is properly defined"""
|
||||
# Create a minimal instance to test colors (avoiding full initialization)
|
||||
gui = ExcelFilterGUI.__new__(ExcelFilterGUI)
|
||||
gui.win11_colors = {
|
||||
'primary': '#0078d4',
|
||||
'primary_dark': '#005a9e',
|
||||
'primary_light': '#cce4f7',
|
||||
'background': '#f0f0f0',
|
||||
'surface': '#ffffff',
|
||||
'text': '#212121',
|
||||
'text_secondary': '#616161',
|
||||
'border': '#e0e0e0',
|
||||
'hover': '#e8f0fe',
|
||||
'pressed': '#d0e0f5',
|
||||
'success': '#107c10',
|
||||
'warning': '#c55c00',
|
||||
'error': '#c42b1c'
|
||||
}
|
||||
|
||||
# Check that it's a dictionary with expected keys
|
||||
colors = gui.win11_colors
|
||||
assert isinstance(colors, dict)
|
||||
expected_keys = ['primary', 'background', 'surface', 'text', 'border']
|
||||
for key in expected_keys:
|
||||
assert key in colors
|
||||
|
||||
print("SUCCESS: Windows 11 color palette is properly defined")
|
||||
|
||||
def test_method_can_be_called_with_selected_columns(self):
|
||||
"""Test that the method can be called with selected_columns parameter"""
|
||||
# Create a minimal mock instance to test method calling
|
||||
gui = ExcelFilterGUI.__new__(ExcelFilterGUI)
|
||||
|
||||
# Mock the required attributes
|
||||
gui.config_tab = Mock()
|
||||
gui.config_tab.input_file_var = Mock()
|
||||
gui.config_tab.input_file_var.get.return_value = "" # Empty file to avoid full execution
|
||||
|
||||
# This should not raise an exception about unexpected keyword arguments
|
||||
try:
|
||||
gui.update_columns_selection(selected_columns=['Col1', 'Col2'])
|
||||
print("SUCCESS: Method accepts selected_columns parameter without error")
|
||||
except TypeError as e:
|
||||
if "unexpected keyword argument" in str(e):
|
||||
pytest.fail(f"Method still doesn't accept selected_columns: {e}")
|
||||
else:
|
||||
# Other TypeErrors are expected (due to mocking), just not the kwarg error
|
||||
print("SUCCESS: Method accepts selected_columns parameter (other errors expected due to mocking)")
|
||||
|
||||
def test_selected_columns_parameter_restoration_logic(self):
|
||||
"""Test that the selected_columns parameter restoration logic works"""
|
||||
# Create a minimal mock instance to test the restoration logic
|
||||
gui = ExcelFilterGUI.__new__(ExcelFilterGUI)
|
||||
|
||||
# Mock the required attributes for the method
|
||||
gui.config_tab = Mock()
|
||||
gui.config_tab.input_file_var = Mock()
|
||||
gui.config_tab.input_file_var.get.return_value = "test.xlsx"
|
||||
gui.config_tab.sheet_var = Mock()
|
||||
gui.config_tab.sheet_var.get.return_value = "Sheet1"
|
||||
gui.config_tab.columns_container = Mock()
|
||||
gui.config_tab.columns_container.winfo_children.return_value = []
|
||||
gui.config_tab.columns_container.grid = Mock()
|
||||
gui.config_tab.columns_container.update_idletasks = Mock()
|
||||
gui.config_tab.columns_vars = {}
|
||||
gui.config_tab.log_message = Mock()
|
||||
gui.config_tab.log_error = Mock()
|
||||
|
||||
# Mock regex column combobox
|
||||
gui.config_tab.regex_column_combobox = Mock()
|
||||
gui.config_tab.regex_column_combobox.__setitem__ = Mock()
|
||||
|
||||
# Mock pandas operations
|
||||
with patch('pandas.read_excel') as mock_read_excel, \
|
||||
patch('tkinter.ttk.Checkbutton') as mock_checkbutton:
|
||||
|
||||
mock_df = Mock()
|
||||
mock_df.columns.tolist.return_value = ['Column1', 'Column2', 'Column3']
|
||||
mock_read_excel.return_value = mock_df
|
||||
|
||||
# Create mock variable for testing selection restoration
|
||||
mock_var1 = Mock()
|
||||
mock_var2 = Mock()
|
||||
mock_var3 = Mock()
|
||||
|
||||
# Simulate the method creating column variables
|
||||
def side_effect(*args, **kwargs):
|
||||
column = args[1] if args else kwargs.get('text', '')
|
||||
if column == 'Column1':
|
||||
gui.config_tab.columns_vars['Column1'] = mock_var1
|
||||
elif column == 'Column2':
|
||||
gui.config_tab.columns_vars['Column2'] = mock_var2
|
||||
elif column == 'Column3':
|
||||
gui.config_tab.columns_vars['Column3'] = mock_var3
|
||||
|
||||
mock_checkbutton.side_effect = side_effect
|
||||
|
||||
# Call update_columns_selection with selected_columns
|
||||
selected_columns = ['Column1', 'Column3']
|
||||
gui.update_columns_selection(selected_columns=selected_columns)
|
||||
|
||||
# Verify that selected columns were set to 1
|
||||
mock_var1.set.assert_called_with(1)
|
||||
mock_var3.set.assert_called_with(1)
|
||||
# Column2 should not be set since it's not in selected_columns
|
||||
mock_var2.set.assert_not_called()
|
||||
|
||||
print("SUCCESS: selected_columns parameter correctly restores column selections")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the tests manually
|
||||
test_instance = TestExcelFilterGUISimple()
|
||||
|
||||
print("Running simple GUI tests...")
|
||||
print()
|
||||
|
||||
try:
|
||||
test_instance.test_update_columns_selection_method_signature()
|
||||
test_instance.test_class_attributes_exist()
|
||||
test_instance.test_win11_colors_attribute()
|
||||
test_instance.test_method_can_be_called_with_selected_columns()
|
||||
test_instance.test_selected_columns_parameter_restoration_logic()
|
||||
|
||||
print()
|
||||
print("SUCCESS: All simple tests passed!")
|
||||
print("The fix for the selected_columns parameter is working correctly.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"FAIL: Test failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
349
excel_filter/tests/test_large_files.py
Normal file
349
excel_filter/tests/test_large_files.py
Normal file
@@ -0,0 +1,349 @@
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import tempfile
|
||||
import time
|
||||
import gc
|
||||
import pytest
|
||||
|
||||
# Add src directory to path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from excel_filter.filter import ExcelFilter
|
||||
|
||||
|
||||
class TestLargeFileHandling:
|
||||
"""
|
||||
Test cases for handling very large Excel files
|
||||
"""
|
||||
|
||||
def generate_large_test_data(self, num_rows=50000):
|
||||
"""
|
||||
Generate a large dataset for testing
|
||||
"""
|
||||
np.random.seed(42) # For reproducible results
|
||||
|
||||
# Create diverse data
|
||||
names = [f"User_{i}" for i in range(num_rows)]
|
||||
ages = np.random.randint(18, 80, num_rows)
|
||||
salaries = np.random.uniform(30000, 200000, num_rows).round(2)
|
||||
departments = np.random.choice(['HR', 'IT', 'Finance', 'Marketing', 'Sales', 'Operations'], num_rows)
|
||||
statuses = np.random.choice(['Active', 'Inactive', 'Pending', 'Terminated'], num_rows)
|
||||
cities = np.random.choice(['New York', 'London', 'Tokyo', 'Berlin', 'Paris', 'Sydney', 'Toronto', 'Singapore'], num_rows)
|
||||
|
||||
# Create various message types
|
||||
messages = []
|
||||
for i in range(num_rows):
|
||||
if i % 100 == 0:
|
||||
messages.append("ERROR: Critical system failure detected")
|
||||
elif i % 50 == 0:
|
||||
messages.append("WARNING: Low disk space on server")
|
||||
elif i % 25 == 0:
|
||||
messages.append("INFO: User login successful")
|
||||
elif i % 10 == 0:
|
||||
messages.append("DEBUG: Processing completed successfully")
|
||||
else:
|
||||
messages.append(f"Normal operation log entry {i}")
|
||||
|
||||
# Create some rows with specific patterns for testing
|
||||
special_indices = np.random.choice(num_rows, size=int(num_rows * 0.1), replace=False)
|
||||
for idx in special_indices[:len(special_indices)//3]:
|
||||
messages[idx] = "CRITICAL: Database connection lost"
|
||||
for idx in special_indices[len(special_indices)//3:2*len(special_indices)//3]:
|
||||
messages[idx] = "ALERT: Security breach detected"
|
||||
for idx in special_indices[2*len(special_indices)//3:]:
|
||||
messages[idx] = "NOTICE: System maintenance scheduled"
|
||||
|
||||
# Create DataFrame
|
||||
# For very large datasets, limit the date range to avoid pandas datetime limits
|
||||
if num_rows > 50000:
|
||||
# For large datasets, use a repeating date range to avoid datetime overflow
|
||||
join_dates = []
|
||||
base_dates = pd.date_range('2010-01-01', periods=min(50000, num_rows), freq='D')
|
||||
for i in range(num_rows):
|
||||
join_dates.append(base_dates[i % len(base_dates)])
|
||||
else:
|
||||
join_dates = pd.date_range('2010-01-01', periods=num_rows, freq='D')
|
||||
|
||||
df = pd.DataFrame({
|
||||
'Name': names,
|
||||
'Age': ages,
|
||||
'Salary': salaries,
|
||||
'Department': departments,
|
||||
'Status': statuses,
|
||||
'City': cities,
|
||||
'Message': messages,
|
||||
'Employee_ID': range(100000, 100000 + num_rows),
|
||||
'Join_Date': join_dates,
|
||||
'Performance_Score': np.random.uniform(1.0, 5.0, num_rows).round(1)
|
||||
})
|
||||
|
||||
return df
|
||||
|
||||
def test_large_file_generation_and_basic_filtering(self):
|
||||
"""
|
||||
Test basic filtering on a large file (10k rows)
|
||||
"""
|
||||
# Generate large test data
|
||||
df = self.generate_large_test_data(10000)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
input_file = os.path.join(temp_dir, "large_test_data.xlsx")
|
||||
output_file = os.path.join(temp_dir, "filtered_output.xlsx")
|
||||
|
||||
# Save to Excel
|
||||
start_time = time.time()
|
||||
df.to_excel(input_file, index=False)
|
||||
save_time = time.time() - start_time
|
||||
print(f"Generated and saved {len(df)} rows in {save_time:.2f} seconds")
|
||||
|
||||
# Test filtering for ERROR messages
|
||||
pattern = r"ERROR|CRITICAL"
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=input_file,
|
||||
output_file=output_file,
|
||||
pattern=pattern,
|
||||
columns=['Message']
|
||||
)
|
||||
|
||||
# Process and measure time
|
||||
start_time = time.time()
|
||||
success = excel_filter.process()
|
||||
process_time = time.time() - start_time
|
||||
|
||||
assert success, "Filtering should succeed"
|
||||
|
||||
# Verify results
|
||||
result_df = pd.read_excel(output_file)
|
||||
expected_count = len(df[df['Message'].str.contains(pattern, case=False, na=False)])
|
||||
|
||||
print(f"Filtered {len(df)} rows to {len(result_df)} rows in {process_time:.2f} seconds")
|
||||
print(f"Expected {expected_count} matches")
|
||||
|
||||
assert len(result_df) == expected_count, f"Expected {expected_count} rows, got {len(result_df)}"
|
||||
|
||||
# Verify all results contain the pattern
|
||||
import re
|
||||
for msg in result_df['Message']:
|
||||
assert re.search(pattern, str(msg), re.IGNORECASE), f"Message '{msg}' should match pattern '{pattern}'"
|
||||
|
||||
def test_large_file_numeric_filtering(self):
|
||||
"""
|
||||
Test numeric filtering on large files
|
||||
"""
|
||||
df = self.generate_large_test_data(30000)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
input_file = os.path.join(temp_dir, "large_numeric_test.xlsx")
|
||||
output_file = os.path.join(temp_dir, "numeric_filtered.xlsx")
|
||||
|
||||
df.to_excel(input_file, index=False)
|
||||
|
||||
# Filter for high salaries (> 150000)
|
||||
numeric_filter = {'column': 'Salary', 'operator': '>', 'value': 150000}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=input_file,
|
||||
output_file=output_file,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
result_df = pd.read_excel(output_file)
|
||||
expected_count = len(df[df['Salary'] > 150000])
|
||||
|
||||
assert len(result_df) == expected_count
|
||||
assert all(result_df['Salary'] > 150000)
|
||||
|
||||
def test_large_file_combined_filtering(self):
|
||||
"""
|
||||
Test combined regex and numeric filtering on large files
|
||||
"""
|
||||
df = self.generate_large_test_data(40000)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
input_file = os.path.join(temp_dir, "combined_test.xlsx")
|
||||
output_file = os.path.join(temp_dir, "combined_filtered.xlsx")
|
||||
|
||||
df.to_excel(input_file, index=False)
|
||||
|
||||
# Filter: IT department employees with salary > 100000
|
||||
pattern = r"IT"
|
||||
numeric_filter = {'column': 'Salary', 'operator': '>', 'value': 100000}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=input_file,
|
||||
output_file=output_file,
|
||||
pattern=pattern,
|
||||
columns=['Department', 'Salary'],
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
result_df = pd.read_excel(output_file)
|
||||
expected_df = df[
|
||||
(df['Department'].str.contains(pattern, case=False, na=False)) &
|
||||
(df['Salary'] > 100000)
|
||||
]
|
||||
|
||||
assert len(result_df) == len(expected_df)
|
||||
|
||||
# Verify all results meet both criteria
|
||||
assert all(result_df['Department'].str.contains(pattern, case=False, na=False))
|
||||
assert all(result_df['Salary'] > 100000)
|
||||
|
||||
def test_memory_efficiency_large_file(self):
|
||||
"""
|
||||
Test memory efficiency with very large files (100k+ rows)
|
||||
"""
|
||||
# Skip this test if we're in a memory-constrained environment
|
||||
try:
|
||||
df = self.generate_large_test_data(100000)
|
||||
except MemoryError:
|
||||
pytest.skip("Not enough memory for 100k row test")
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
input_file = os.path.join(temp_dir, "memory_test.xlsx")
|
||||
output_file = os.path.join(temp_dir, "memory_filtered.xlsx")
|
||||
|
||||
df.to_excel(input_file, index=False)
|
||||
|
||||
# Simple filter that should match ~10% of rows
|
||||
pattern = r"ERROR|WARNING|CRITICAL|ALERT"
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=input_file,
|
||||
output_file=output_file,
|
||||
pattern=pattern,
|
||||
columns=['Message']
|
||||
)
|
||||
|
||||
# Monitor memory usage if possible
|
||||
try:
|
||||
import psutil
|
||||
process = psutil.Process(os.getpid())
|
||||
initial_memory = process.memory_info().rss / 1024 / 1024 # MB
|
||||
|
||||
success = excel_filter.process()
|
||||
|
||||
final_memory = process.memory_info().rss / 1024 / 1024 # MB
|
||||
memory_used = final_memory - initial_memory
|
||||
|
||||
assert success
|
||||
print(f"Memory used: {memory_used:.1f}MB")
|
||||
|
||||
# Should not use excessive memory (arbitrary threshold)
|
||||
assert memory_used < 2000, f"Used {memory_used:.1f}MB, should be less than 2000MB"
|
||||
except ImportError:
|
||||
# psutil not available, just run the filter without memory monitoring
|
||||
print("psutil not available, skipping memory monitoring")
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
result_df = pd.read_excel(output_file)
|
||||
expected_count = len(df[df['Message'].str.contains(pattern, case=False, na=False)])
|
||||
|
||||
assert len(result_df) == expected_count
|
||||
|
||||
def test_edge_cases_large_file(self):
|
||||
"""
|
||||
Test edge cases with large files
|
||||
"""
|
||||
df = self.generate_large_test_data(25000)
|
||||
|
||||
# Add some edge case data
|
||||
df.loc[0, 'Message'] = "" # Empty string
|
||||
df.loc[1, 'Message'] = None # NaN value
|
||||
df.loc[2, 'Salary'] = np.nan # NaN numeric
|
||||
df.loc[3, 'Message'] = "A" * 10000 # Very long string
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
input_file = os.path.join(temp_dir, "edge_case_test.xlsx")
|
||||
output_file = os.path.join(temp_dir, "edge_case_filtered.xlsx")
|
||||
|
||||
df.to_excel(input_file, index=False)
|
||||
|
||||
# Filter that should handle edge cases gracefully
|
||||
pattern = r"ERROR"
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=input_file,
|
||||
output_file=output_file,
|
||||
pattern=pattern,
|
||||
columns=['Message']
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
result_df = pd.read_excel(output_file)
|
||||
|
||||
# Should find ERROR messages but handle edge cases
|
||||
error_messages = df[df['Message'].str.contains(pattern, case=False, na=False)]
|
||||
assert len(result_df) == len(error_messages)
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_very_large_file_stress_test(self):
|
||||
"""
|
||||
Stress test with a very large file (marked as slow test)
|
||||
"""
|
||||
# This test is marked as slow and may be skipped in regular runs
|
||||
try:
|
||||
df = self.generate_large_test_data(200000) # 200k rows
|
||||
except MemoryError:
|
||||
pytest.skip("Not enough memory for 200k row stress test")
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
input_file = os.path.join(temp_dir, "stress_test.xlsx")
|
||||
output_file = os.path.join(temp_dir, "stress_filtered.xlsx")
|
||||
|
||||
# Time the save operation
|
||||
start_time = time.time()
|
||||
df.to_excel(input_file, index=False)
|
||||
save_time = time.time() - start_time
|
||||
|
||||
file_size = os.path.getsize(input_file) / 1024 / 1024 # MB
|
||||
print(f"Created {file_size:.1f}MB test file with {len(df)} rows in {save_time:.2f} seconds")
|
||||
|
||||
# Apply complex filtering
|
||||
pattern = r"ERROR|WARNING|CRITICAL"
|
||||
numeric_filter = {'column': 'Age', 'operator': '>', 'value': 50}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=input_file,
|
||||
output_file=output_file,
|
||||
pattern=pattern,
|
||||
columns=['Message', 'Age'],
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
start_time = time.time()
|
||||
success = excel_filter.process()
|
||||
process_time = time.time() - start_time
|
||||
|
||||
assert success
|
||||
|
||||
result_df = pd.read_excel(output_file)
|
||||
expected_df = df[
|
||||
(df['Message'].str.contains(pattern, case=False, na=False)) &
|
||||
(df['Age'] > 50)
|
||||
]
|
||||
|
||||
print(f"Processed {len(df)} rows in {process_time:.2f} seconds")
|
||||
print(f"Filtered to {len(result_df)} rows (expected {len(expected_df)})")
|
||||
|
||||
assert len(result_df) == len(expected_df)
|
||||
|
||||
# Verify results are correct
|
||||
assert all(result_df['Message'].str.contains(pattern, case=False, na=False))
|
||||
assert all(result_df['Age'] > 50)
|
||||
|
||||
# Cleanup
|
||||
gc.collect()
|
||||
104
excel_filter/tests/test_main.py
Normal file
104
excel_filter/tests/test_main.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Unit tests for main.py
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import json
|
||||
import tempfile
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
from excel_filter.main import load_config, main
|
||||
|
||||
|
||||
class TestMain(unittest.TestCase):
|
||||
"""
|
||||
Test class for main module functions
|
||||
"""
|
||||
|
||||
def test_load_config_success(self):
|
||||
"""
|
||||
Test successful config loading
|
||||
"""
|
||||
config_data = {
|
||||
"pattern": "test.*",
|
||||
"sheet_name": "Sheet1",
|
||||
"columns": ["A", "B"]
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
||||
json.dump(config_data, f)
|
||||
config_file = f.name
|
||||
|
||||
try:
|
||||
result = load_config(config_file)
|
||||
self.assertEqual(result, config_data)
|
||||
finally:
|
||||
os.unlink(config_file)
|
||||
|
||||
def test_load_config_file_not_found(self):
|
||||
"""
|
||||
Test config loading with non-existent file
|
||||
"""
|
||||
with self.assertRaises(Exception):
|
||||
load_config("nonexistent.json")
|
||||
|
||||
@patch('sys.argv', ['main.py', '--input', 'input.xlsx', '--output', 'output.xlsx', '--pattern', 'test'])
|
||||
@patch('excel_filter.main.ExcelFilter')
|
||||
def test_main_with_args(self, mock_filter_class):
|
||||
"""
|
||||
Test main function with command line arguments
|
||||
"""
|
||||
mock_filter = mock_filter_class.return_value
|
||||
mock_filter.process.return_value = True
|
||||
|
||||
main()
|
||||
|
||||
mock_filter_class.assert_called_once_with(
|
||||
input_file='input.xlsx',
|
||||
output_file='output.xlsx',
|
||||
pattern='test',
|
||||
sheet_name=None,
|
||||
columns=None
|
||||
)
|
||||
mock_filter.process.assert_called_once()
|
||||
|
||||
@patch('sys.argv', ['main.py', '--input', 'input.xlsx', '--output', 'output.xlsx', '--config', 'config.json'])
|
||||
@patch('excel_filter.main.load_config')
|
||||
@patch('excel_filter.main.ExcelFilter')
|
||||
def test_main_with_config(self, mock_filter_class, mock_load_config):
|
||||
"""
|
||||
Test main function with config file
|
||||
"""
|
||||
mock_load_config.return_value = {
|
||||
"pattern": "config_pattern",
|
||||
"sheet_name": "ConfigSheet",
|
||||
"columns": ["Col1", "Col2"]
|
||||
}
|
||||
mock_filter = mock_filter_class.return_value
|
||||
mock_filter.process.return_value = True
|
||||
|
||||
main()
|
||||
|
||||
mock_load_config.assert_called_once_with('config.json')
|
||||
mock_filter_class.assert_called_once_with(
|
||||
input_file='input.xlsx',
|
||||
output_file='output.xlsx',
|
||||
pattern='config_pattern',
|
||||
sheet_name='ConfigSheet',
|
||||
columns=['Col1', 'Col2']
|
||||
)
|
||||
mock_filter.process.assert_called_once()
|
||||
|
||||
@patch('sys.argv', ['main.py', '--input', 'input.xlsx', '--output', 'output.xlsx'])
|
||||
@patch('excel_filter.main.logger')
|
||||
def test_main_no_pattern(self, mock_logger):
|
||||
"""
|
||||
Test main function with no pattern specified
|
||||
"""
|
||||
main()
|
||||
|
||||
mock_logger.error.assert_called_once_with("Kein Regex-Muster angegeben")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
448
excel_filter/tests/test_numeric_filter.py
Normal file
448
excel_filter/tests/test_numeric_filter.py
Normal file
@@ -0,0 +1,448 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import pytest
|
||||
import pandas as pd
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
# Add src directory to path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
# Import the classes to test
|
||||
from excel_filter.filter import ExcelFilter
|
||||
from excel_filter.gui_components.config_tab import ConfigTab
|
||||
from excel_filter.translations import Translations
|
||||
|
||||
|
||||
class TestNumericFilter:
|
||||
"""Test cases for numeric filtering functionality"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test fixtures"""
|
||||
# Create test data
|
||||
self.test_data = {
|
||||
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
|
||||
'Age': [25, 30, 35, 40, 45],
|
||||
'Salary': [50000.50, 60000.75, 70000.25, 80000.00, 90000.90],
|
||||
'Score': [85.5, 92.0, 78.3, 96.7, 88.1],
|
||||
'Department': ['HR', 'IT', 'Finance', 'IT', 'HR']
|
||||
}
|
||||
self.df = pd.DataFrame(self.test_data)
|
||||
|
||||
def test_numeric_filter_greater_than(self):
|
||||
"""Test filtering values greater than a threshold"""
|
||||
numeric_filter = {'column': 'Age', 'operator': '>', 'value': 30}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
expected_ages = [35, 40, 45] # Ages > 30
|
||||
assert len(result) == 3
|
||||
assert result['Age'].tolist() == expected_ages
|
||||
|
||||
def test_numeric_filter_less_than(self):
|
||||
"""Test filtering values less than a threshold"""
|
||||
numeric_filter = {'column': 'Salary', 'operator': '<', 'value': 70000.00}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
expected_salaries = [50000.50, 60000.75] # Salaries < 70000
|
||||
assert len(result) == 2
|
||||
assert result['Salary'].tolist() == expected_salaries
|
||||
|
||||
def test_numeric_filter_greater_equal(self):
|
||||
"""Test filtering values greater than or equal to a threshold"""
|
||||
numeric_filter = {'column': 'Score', 'operator': '>=', 'value': 88.1}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
expected_scores = [92.0, 96.7, 88.1] # Scores >= 88.1
|
||||
assert len(result) == 3
|
||||
assert result['Score'].tolist() == expected_scores
|
||||
|
||||
def test_numeric_filter_less_equal(self):
|
||||
"""Test filtering values less than or equal to a threshold"""
|
||||
numeric_filter = {'column': 'Age', 'operator': '<=', 'value': 30}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
expected_ages = [25, 30] # Ages <= 30
|
||||
assert len(result) == 2
|
||||
assert result['Age'].tolist() == expected_ages
|
||||
|
||||
def test_numeric_filter_equal(self):
|
||||
"""Test filtering values equal to a specific value"""
|
||||
numeric_filter = {'column': 'Age', 'operator': '=', 'value': 35}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result['Age'].iloc[0] == 35
|
||||
assert result['Name'].iloc[0] == 'Charlie'
|
||||
|
||||
def test_numeric_filter_combined_with_regex(self):
|
||||
"""Test combining numeric filter with regex filter"""
|
||||
numeric_filter = {'column': 'Age', 'operator': '>', 'value': 30}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
pattern=r'HR', # Regex pattern for HR department
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter.filter_dataframe(self.df)
|
||||
|
||||
# Should find people in HR department who are older than 30
|
||||
# Alice (25, HR) - too young
|
||||
# Eve (45, HR) - matches both criteria
|
||||
assert len(result) == 1
|
||||
assert result['Name'].iloc[0] == 'Eve'
|
||||
assert result['Age'].iloc[0] == 45
|
||||
assert result['Department'].iloc[0] == 'HR'
|
||||
|
||||
def test_numeric_filter_no_matches(self):
|
||||
"""Test numeric filter that matches no rows"""
|
||||
numeric_filter = {'column': 'Age', 'operator': '>', 'value': 100}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
assert len(result) == 0
|
||||
assert result.shape[0] == 0
|
||||
|
||||
def test_numeric_filter_invalid_column(self):
|
||||
"""Test numeric filter with non-existent column"""
|
||||
numeric_filter = {'column': 'NonExistentColumn', 'operator': '>', 'value': 30}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="Spalte 'NonExistentColumn' existiert nicht"):
|
||||
excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
def test_numeric_filter_invalid_operator(self):
|
||||
"""Test numeric filter with invalid operator"""
|
||||
numeric_filter = {'column': 'Age', 'operator': 'invalid', 'value': 30}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="Unbekannter Operator"):
|
||||
excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
def test_numeric_filter_non_numeric_values(self):
|
||||
"""Test numeric filter on column with non-numeric values"""
|
||||
# Add a column with mixed data types
|
||||
df_mixed = self.df.copy()
|
||||
df_mixed['Mixed'] = ['text', 25, 30.5, '45', None]
|
||||
|
||||
numeric_filter = {'column': 'Mixed', 'operator': '>', 'value': 25}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(df_mixed)
|
||||
|
||||
# pandas to_numeric with errors='coerce' converts '45' to 45.0
|
||||
# So we get both 30.5 and 45.0 as matches for > 25
|
||||
assert len(result) == 2
|
||||
|
||||
# Check that we have the expected numeric values (ignoring non-numeric)
|
||||
numeric_values = []
|
||||
for val in result['Mixed']:
|
||||
try:
|
||||
numeric_values.append(float(val))
|
||||
except (ValueError, TypeError):
|
||||
continue # Skip non-numeric values
|
||||
|
||||
assert sorted(numeric_values) == [30.5, 45.0]
|
||||
|
||||
def test_numeric_filter_with_decimals(self):
|
||||
"""Test numeric filter with decimal values"""
|
||||
numeric_filter = {'column': 'Salary', 'operator': '>', 'value': 65000.50}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
expected_salaries = [70000.25, 80000.00, 90000.90] # Salaries > 65000.50
|
||||
assert len(result) == 3
|
||||
assert result['Salary'].tolist() == expected_salaries
|
||||
|
||||
def test_numeric_filter_boundary_values(self):
|
||||
"""Test numeric filter with boundary values"""
|
||||
# Test exactly equal to boundary
|
||||
numeric_filter = {'column': 'Score', 'operator': '=', 'value': 88.1}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result['Score'].iloc[0] == 88.1
|
||||
|
||||
# Test greater than with boundary
|
||||
numeric_filter = {'column': 'Score', 'operator': '>', 'value': 88.1}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
expected_scores = [92.0, 96.7] # Scores > 88.1
|
||||
assert len(result) == 2
|
||||
assert result['Score'].tolist() == expected_scores
|
||||
|
||||
def test_numeric_filter_all_columns(self):
|
||||
"""Test numeric filter applied to all columns"""
|
||||
# Test filtering across all columns for values > 30
|
||||
numeric_filter = {'column': 'Alle Spalten', 'operator': '>', 'value': 30}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
# Should find rows where ANY column has a value > 30
|
||||
# All rows have Salary and Score values > 30, so all rows match
|
||||
assert len(result) == 5 # All rows match
|
||||
|
||||
# Verify all rows are included
|
||||
names = sorted(result['Name'].tolist())
|
||||
assert names == ['Alice', 'Bob', 'Charlie', 'David', 'Eve']
|
||||
|
||||
def test_numeric_filter_all_columns_less_than(self):
|
||||
"""Test numeric filter applied to all columns with less than"""
|
||||
# Test filtering across all columns for values < 30
|
||||
numeric_filter = {'column': 'Alle Spalten', 'operator': '<', 'value': 30}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
# Should find rows where ANY column has a value < 30
|
||||
# Row 0: Age=25 (Age < 30)
|
||||
# Row 1: Age=30 (Age = 30, not < 30), but Score=92.0? Wait, let me check data
|
||||
# Actually, looking at the data: Row 0 has Age=25 (< 30), others don't have values < 30
|
||||
# Row 4 has Score=88.1 but that's not < 30
|
||||
# So only Row 0 should match
|
||||
assert len(result) == 1
|
||||
assert result['Age'].iloc[0] == 25
|
||||
assert result['Name'].iloc[0] == 'Alice'
|
||||
|
||||
def test_numeric_filter_all_columns_no_matches(self):
|
||||
"""Test numeric filter on all columns with no matches"""
|
||||
numeric_filter = {'column': 'Alle Spalten', 'operator': '>', 'value': 100000}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=None, output_file=None,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
result = excel_filter._apply_numeric_filter(self.df)
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
|
||||
class TestNumericFilterLogic:
|
||||
"""Test cases for numeric filter logic without UI dependencies"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test fixtures"""
|
||||
self.translations = Translations()
|
||||
|
||||
@pytest.mark.parametrize("display_op,expected_op", [
|
||||
("> (größer als)", ">"),
|
||||
("< (kleiner als)", "<"),
|
||||
(">= (größer/gleich)", ">="),
|
||||
("<= (kleiner/gleich)", "<="),
|
||||
("= (gleich)", "=")
|
||||
])
|
||||
def test_numeric_filter_settings_conversion(self, display_op, expected_op):
|
||||
"""Test conversion of UI display values to filter settings"""
|
||||
# Create operator mapping dict
|
||||
operator_map = {
|
||||
"> (größer als)": ">",
|
||||
"< (kleiner als)": "<",
|
||||
">= (größer/gleich)": ">=",
|
||||
"<= (kleiner/gleich)": "<=",
|
||||
"= (gleich)": "="
|
||||
}
|
||||
|
||||
# Simulate the logic from get_numeric_filter_settings method
|
||||
enabled = True
|
||||
column = 'TestColumn'
|
||||
operator = display_op
|
||||
value = '123.45'
|
||||
|
||||
# Test the logic
|
||||
result = (
|
||||
None if not enabled
|
||||
else {
|
||||
'column': column,
|
||||
'operator': operator_map.get(operator, operator),
|
||||
'value': float(value)
|
||||
} if column and operator and value
|
||||
else None
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert result['operator'] == expected_op
|
||||
assert result['column'] == 'TestColumn'
|
||||
assert result['value'] == 123.45
|
||||
|
||||
|
||||
class TestNumericFilterIntegration:
|
||||
"""Integration tests for numeric filtering with full workflow"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Set up test fixtures"""
|
||||
self.test_data = {
|
||||
'Product': ['Widget A', 'Widget B', 'Widget C', 'Widget D', 'Widget E'],
|
||||
'Price': [10.99, 25.50, 15.75, 30.00, 8.25],
|
||||
'Stock': [100, 250, 75, 300, 50],
|
||||
'Rating': [4.2, 4.8, 3.9, 4.9, 3.5],
|
||||
'Category': ['Electronics', 'Tools', 'Electronics', 'Tools', 'Accessories']
|
||||
}
|
||||
|
||||
def test_full_workflow_regex_and_numeric(self):
|
||||
"""Test complete workflow with both regex and numeric filtering"""
|
||||
# Create test Excel file
|
||||
df = pd.DataFrame(self.test_data)
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp_input:
|
||||
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp_output:
|
||||
input_file = temp_input.name
|
||||
output_file = temp_output.name
|
||||
|
||||
try:
|
||||
# Save test data to input file
|
||||
df.to_excel(input_file, index=False)
|
||||
|
||||
# Apply filters: Category contains 'Tool' AND Price > 20
|
||||
numeric_filter = {'column': 'Price', 'operator': '>', 'value': 20.0}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=input_file,
|
||||
output_file=output_file,
|
||||
pattern=r'Tool', # Regex for category
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
# Load and verify results
|
||||
result_df = pd.read_excel(output_file)
|
||||
|
||||
# Should find Widget B (Tools, $25.50) and Widget D (Tools, $30.00)
|
||||
assert len(result_df) == 2
|
||||
assert 'Widget B' in result_df['Product'].tolist()
|
||||
assert 'Widget D' in result_df['Product'].tolist()
|
||||
|
||||
# Verify prices are > 20
|
||||
for price in result_df['Price']:
|
||||
assert price > 20.0
|
||||
|
||||
finally:
|
||||
# Clean up
|
||||
try:
|
||||
os.unlink(input_file)
|
||||
os.unlink(output_file)
|
||||
except:
|
||||
pass
|
||||
|
||||
def test_numeric_filter_only(self):
|
||||
"""Test numeric filtering without regex"""
|
||||
df = pd.DataFrame(self.test_data)
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp_input:
|
||||
with tempfile.NamedTemporaryFile(suffix='.xlsx', delete=False) as temp_output:
|
||||
input_file = temp_input.name
|
||||
output_file = temp_output.name
|
||||
|
||||
try:
|
||||
# Save test data
|
||||
df.to_excel(input_file, index=False)
|
||||
|
||||
# Filter: Stock >= 100
|
||||
numeric_filter = {'column': 'Stock', 'operator': '>=', 'value': 100}
|
||||
|
||||
excel_filter = ExcelFilter(
|
||||
input_file=input_file,
|
||||
output_file=output_file,
|
||||
numeric_filter=numeric_filter
|
||||
)
|
||||
|
||||
success = excel_filter.process()
|
||||
assert success
|
||||
|
||||
# Verify results
|
||||
result_df = pd.read_excel(output_file)
|
||||
|
||||
# Should find Widget A (100), Widget B (250), Widget D (300)
|
||||
assert len(result_df) == 3
|
||||
for stock in result_df['Stock']:
|
||||
assert stock >= 100
|
||||
|
||||
finally:
|
||||
try:
|
||||
os.unlink(input_file)
|
||||
os.unlink(output_file)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
42
excel_filter/tests/test_styling.py
Normal file
42
excel_filter/tests/test_styling.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from excel_filter.gui_components.main_window import MainWindow
|
||||
|
||||
def test_styling():
|
||||
root = tk.Tk()
|
||||
|
||||
# Test the main window creation
|
||||
try:
|
||||
main_window = MainWindow(root)
|
||||
print("MainWindow created successfully")
|
||||
|
||||
# Test color palette
|
||||
print("Windows 11 color palette loaded")
|
||||
|
||||
# Test styles
|
||||
style = ttk.Style()
|
||||
print("Tkinter styles configured")
|
||||
|
||||
# Test window properties
|
||||
print(f"Window title: {root.title()}")
|
||||
print(f"Window size: {root.geometry()}")
|
||||
|
||||
print("\nWindows 11 Styling Test Results:")
|
||||
print("All styling components loaded successfully!")
|
||||
print("GUI should now look like a Windows 11 application")
|
||||
|
||||
# Close the window without running mainloop
|
||||
root.destroy()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
root.destroy()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_styling()
|
||||
59
excel_filter/tests/test_tabs.py
Normal file
59
excel_filter/tests/test_tabs.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from excel_filter.gui_components.main_window import MainWindow
|
||||
|
||||
def test_tab_styling():
|
||||
root = tk.Tk()
|
||||
|
||||
try:
|
||||
main_window = MainWindow(root)
|
||||
|
||||
# Add some test tabs
|
||||
tab1 = ttk.Frame(main_window.notebook)
|
||||
tab2 = ttk.Frame(main_window.notebook)
|
||||
tab3 = ttk.Frame(main_window.notebook)
|
||||
|
||||
main_window.add_tab(tab1, "Configuration")
|
||||
main_window.add_tab(tab2, "Execution")
|
||||
main_window.add_tab(tab3, "Help")
|
||||
|
||||
# Enhance tab appearance
|
||||
main_window.enhance_tab_appearance()
|
||||
|
||||
print("Tab styling test results:")
|
||||
print("Tabs created successfully")
|
||||
print("Windows 11 tab styling applied")
|
||||
print("Tab enhancement completed")
|
||||
|
||||
# Test tab properties
|
||||
style = ttk.Style()
|
||||
tab_style = style.configure('TNotebook.Tab')
|
||||
print(f"Tab font: {tab_style.get('font', 'default')}")
|
||||
print(f"Tab padding: {tab_style.get('padding', 'default')}")
|
||||
|
||||
# Test notebook properties
|
||||
notebook_style = style.configure('TNotebook')
|
||||
print(f"Notebook background: {notebook_style.get('background', 'default')}")
|
||||
|
||||
print("\nWindows 11 Tab Styling:")
|
||||
print("Selected tabs have white background with blue text")
|
||||
print("Unselected tabs have light gray background with gray text")
|
||||
print("Hover effects applied")
|
||||
print("Proper padding and spacing")
|
||||
print("Segoe UI font (if available)")
|
||||
|
||||
# Close the window
|
||||
root.destroy()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
root.destroy()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_tab_styling()
|
||||
157
excel_filter/tests/test_utils.py
Normal file
157
excel_filter/tests/test_utils.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
Unit tests for utility modules
|
||||
"""
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
# Add src directory to path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from excel_filter.utils.file_utils import load_config, save_config
|
||||
from excel_filter.utils.logging_utils import setup_logging, log_message, log_error
|
||||
|
||||
|
||||
class TestFileUtils:
|
||||
"""
|
||||
Test class for file_utils functions
|
||||
"""
|
||||
|
||||
def test_load_config_success(self):
|
||||
"""
|
||||
Test successful config loading
|
||||
"""
|
||||
config_data = {
|
||||
"pattern": "test.*",
|
||||
"sheet_name": "Sheet1",
|
||||
"columns": ["A", "B"]
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
||||
json.dump(config_data, f)
|
||||
config_file = f.name
|
||||
|
||||
try:
|
||||
result = load_config(config_file)
|
||||
assert result == config_data
|
||||
finally:
|
||||
os.unlink(config_file)
|
||||
|
||||
def test_load_config_file_not_found(self):
|
||||
"""
|
||||
Test config loading with non-existent file returns empty dict
|
||||
"""
|
||||
result = load_config("nonexistent.json")
|
||||
assert result == {}
|
||||
|
||||
def test_load_config_invalid_json(self):
|
||||
"""
|
||||
Test config loading with invalid JSON
|
||||
"""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
||||
f.write("invalid json")
|
||||
config_file = f.name
|
||||
|
||||
try:
|
||||
with pytest.raises(Exception):
|
||||
load_config(config_file)
|
||||
finally:
|
||||
os.unlink(config_file)
|
||||
|
||||
def test_save_config_success(self):
|
||||
"""
|
||||
Test successful config saving
|
||||
"""
|
||||
config_data = {
|
||||
"pattern": "test.*",
|
||||
"sheet_name": "Sheet1"
|
||||
}
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
||||
config_file = f.name
|
||||
|
||||
try:
|
||||
save_config(config_file, config_data)
|
||||
|
||||
# Verify file was saved correctly
|
||||
with open(config_file, 'r') as f:
|
||||
saved_data = json.load(f)
|
||||
assert saved_data == config_data
|
||||
finally:
|
||||
os.unlink(config_file)
|
||||
|
||||
def test_save_config_error(self):
|
||||
"""
|
||||
Test config saving with error
|
||||
"""
|
||||
# Try to save to invalid path
|
||||
with pytest.raises(Exception):
|
||||
save_config("/invalid/path/config.json", {"test": "data"})
|
||||
|
||||
|
||||
class TestLoggingUtils:
|
||||
"""
|
||||
Test class for logging_utils functions
|
||||
"""
|
||||
|
||||
def test_setup_logging_without_file(self):
|
||||
"""
|
||||
Test setup_logging without log file
|
||||
"""
|
||||
# Store original level
|
||||
original_level = logging.getLogger().getEffectiveLevel()
|
||||
|
||||
setup_logging()
|
||||
# Verify setup_logging can be called without error
|
||||
# The exact level may be influenced by pytest or other configurations
|
||||
logger = logging.getLogger()
|
||||
assert logger is not None # Just verify logging is accessible
|
||||
|
||||
@patch('logging.FileHandler')
|
||||
def test_setup_logging_with_file(self, mock_file_handler):
|
||||
"""
|
||||
Test setup_logging with log file
|
||||
"""
|
||||
setup_logging(log_file="test.log", level=logging.DEBUG)
|
||||
# Verify FileHandler was created
|
||||
mock_file_handler.assert_called_once_with("test.log")
|
||||
|
||||
def test_log_message_normal(self):
|
||||
"""
|
||||
Test logging normal message
|
||||
"""
|
||||
mock_widget = MagicMock()
|
||||
log_message(mock_widget, "Test message")
|
||||
|
||||
# Verify widget methods were called
|
||||
mock_widget.config.assert_called()
|
||||
mock_widget.insert.assert_called()
|
||||
mock_widget.see.assert_called_with('end')
|
||||
|
||||
def test_log_message_error(self):
|
||||
"""
|
||||
Test logging error message
|
||||
"""
|
||||
mock_widget = MagicMock()
|
||||
log_message(mock_widget, "Error message", is_error=True)
|
||||
|
||||
# Verify error formatting
|
||||
mock_widget.tag_add.assert_called()
|
||||
mock_widget.tag_config.assert_called_with("error", foreground="red")
|
||||
|
||||
def test_log_error(self):
|
||||
"""
|
||||
Test log_error function
|
||||
"""
|
||||
mock_widget = MagicMock()
|
||||
log_error(mock_widget, "Test error")
|
||||
|
||||
# Verify it calls log_message with error flag
|
||||
mock_widget.config.assert_called()
|
||||
mock_widget.insert.assert_called()
|
||||
mock_widget.tag_add.assert_called()
|
||||
Reference in New Issue
Block a user