275 lines
8.4 KiB
Python
275 lines
8.4 KiB
Python
"""
|
|
Test suite for Clipboard Manager
|
|
|
|
Run with: python -m pytest tests/test_clipboard.py -v
|
|
Or: python tests/test_clipboard.py
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
from pathlib import Path
|
|
|
|
# Add parent to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from core.clipboard import ClipboardManager, ClipboardEntry, get_clipboard_manager
|
|
|
|
|
|
class TestClipboardManager:
|
|
"""Tests for ClipboardManager functionality."""
|
|
|
|
def setup_method(self):
|
|
"""Setup for each test - use temp file for history."""
|
|
# Reset singleton for clean tests
|
|
ClipboardManager._instance = None
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.history_file = os.path.join(self.temp_dir, "test_history.json")
|
|
self.manager = ClipboardManager(history_file=self.history_file)
|
|
|
|
def teardown_method(self):
|
|
"""Cleanup after each test."""
|
|
self.manager.stop_monitoring()
|
|
ClipboardManager._instance = None
|
|
# Clean up temp file
|
|
if os.path.exists(self.history_file):
|
|
os.remove(self.history_file)
|
|
os.rmdir(self.temp_dir)
|
|
|
|
def test_singleton(self):
|
|
"""Test that ClipboardManager is a singleton."""
|
|
manager1 = get_clipboard_manager()
|
|
manager2 = get_clipboard_manager()
|
|
assert manager1 is manager2
|
|
|
|
def test_copy_and_paste(self):
|
|
"""Test basic copy and paste functionality."""
|
|
test_text = "Hello, Clipboard!"
|
|
|
|
# Copy
|
|
result = self.manager.copy(test_text)
|
|
assert result is True
|
|
|
|
# Paste
|
|
pasted = self.manager.paste()
|
|
assert pasted == test_text
|
|
|
|
def test_history_tracking(self):
|
|
"""Test that copies are tracked in history."""
|
|
texts = ["First", "Second", "Third"]
|
|
|
|
for text in texts:
|
|
self.manager.copy(text, source="test")
|
|
time.sleep(0.05) # Small delay for timestamp differences
|
|
|
|
history = self.manager.get_history()
|
|
|
|
# Should have 3 entries (most recent first)
|
|
assert len(history) == 3
|
|
assert history[0].content == "Third"
|
|
assert history[1].content == "Second"
|
|
assert history[2].content == "First"
|
|
|
|
def test_history_limit(self):
|
|
"""Test that history is limited to MAX_HISTORY_SIZE."""
|
|
# Copy more than max entries
|
|
for i in range(110):
|
|
self.manager.copy(f"Entry {i}")
|
|
|
|
history = self.manager.get_history()
|
|
assert len(history) == 100 # MAX_HISTORY_SIZE
|
|
|
|
def test_no_duplicate_consecutive_entries(self):
|
|
"""Test that duplicate consecutive entries aren't added."""
|
|
self.manager.copy("Same text")
|
|
self.manager.copy("Same text")
|
|
self.manager.copy("Same text")
|
|
|
|
history = self.manager.get_history()
|
|
assert len(history) == 1
|
|
|
|
def test_history_persistence(self):
|
|
"""Test that history is saved and loaded correctly."""
|
|
# Add some entries
|
|
self.manager.copy("Persistent", source="test")
|
|
time.sleep(0.1) # Let async save complete
|
|
|
|
# Force save
|
|
self.manager._save_history()
|
|
|
|
# Verify file exists and has content
|
|
assert os.path.exists(self.history_file)
|
|
|
|
with open(self.history_file, 'r') as f:
|
|
data = json.load(f)
|
|
assert 'history' in data
|
|
assert len(data['history']) >= 1
|
|
|
|
def test_clear_history(self):
|
|
"""Test clearing history."""
|
|
self.manager.copy("To be cleared")
|
|
assert len(self.manager.get_history()) == 1
|
|
|
|
self.manager.clear_history()
|
|
assert len(self.manager.get_history()) == 0
|
|
|
|
def test_thread_safety(self):
|
|
"""Test thread-safe operations."""
|
|
errors = []
|
|
|
|
def copy_worker(text):
|
|
try:
|
|
for i in range(10):
|
|
self.manager.copy(f"{text}_{i}")
|
|
except Exception as e:
|
|
errors.append(e)
|
|
|
|
# Run multiple threads
|
|
threads = []
|
|
for i in range(5):
|
|
t = threading.Thread(target=copy_worker, args=(f"Thread{i}",))
|
|
threads.append(t)
|
|
t.start()
|
|
|
|
for t in threads:
|
|
t.join()
|
|
|
|
assert len(errors) == 0, f"Thread errors: {errors}"
|
|
|
|
def test_has_changed(self):
|
|
"""Test clipboard change detection."""
|
|
# Initial state
|
|
self.manager.copy("Initial")
|
|
assert self.manager.has_changed() is False # Same content
|
|
|
|
# Manually simulate external change (mock by copying new content)
|
|
self.manager.copy("Changed")
|
|
# Note: has_changed() compares to _last_content which is updated on copy
|
|
# So we need to simulate external change differently
|
|
old_content = self.manager._last_content
|
|
self.manager._last_content = "Different"
|
|
assert self.manager.has_changed() is True
|
|
|
|
def test_get_history_limit(self):
|
|
"""Test getting limited history."""
|
|
for i in range(10):
|
|
self.manager.copy(f"Entry {i}")
|
|
|
|
limited = self.manager.get_history(limit=5)
|
|
assert len(limited) == 5
|
|
|
|
def test_entry_metadata(self):
|
|
"""Test that entries have correct metadata."""
|
|
self.manager.copy("Test content", source="my_plugin")
|
|
|
|
history = self.manager.get_history()
|
|
entry = history[0]
|
|
|
|
assert entry.content == "Test content"
|
|
assert entry.source == "my_plugin"
|
|
assert entry.timestamp is not None
|
|
|
|
def test_monitoring(self):
|
|
"""Test clipboard monitoring."""
|
|
changes = []
|
|
|
|
def on_change(content):
|
|
changes.append(content)
|
|
|
|
self.manager.start_monitoring(callback=on_change)
|
|
assert self.manager.is_monitoring() is True
|
|
|
|
time.sleep(0.1)
|
|
|
|
self.manager.stop_monitoring()
|
|
assert self.manager.is_monitoring() is False
|
|
|
|
def test_stats(self):
|
|
"""Test getting stats."""
|
|
self.manager.copy("Stat test")
|
|
stats = self.manager.get_stats()
|
|
|
|
assert 'history_count' in stats
|
|
assert 'max_history' in stats
|
|
assert 'is_available' in stats
|
|
assert stats['max_history'] == 100
|
|
|
|
|
|
class TestClipboardEntry:
|
|
"""Tests for ClipboardEntry dataclass."""
|
|
|
|
def test_to_dict(self):
|
|
"""Test conversion to dictionary."""
|
|
entry = ClipboardEntry(
|
|
content="Test",
|
|
timestamp="2024-01-01T00:00:00",
|
|
source="test_plugin"
|
|
)
|
|
|
|
d = entry.to_dict()
|
|
assert d['content'] == "Test"
|
|
assert d['timestamp'] == "2024-01-01T00:00:00"
|
|
assert d['source'] == "test_plugin"
|
|
|
|
def test_from_dict(self):
|
|
"""Test creation from dictionary."""
|
|
data = {
|
|
'content': 'Test',
|
|
'timestamp': '2024-01-01T00:00:00',
|
|
'source': 'test_plugin'
|
|
}
|
|
|
|
entry = ClipboardEntry.from_dict(data)
|
|
assert entry.content == "Test"
|
|
assert entry.timestamp == "2024-01-01T00:00:00"
|
|
assert entry.source == "test_plugin"
|
|
|
|
|
|
def run_manual_test():
|
|
"""Run a manual demonstration of clipboard features."""
|
|
print("\n" + "="*60)
|
|
print("MANUAL CLIPBOARD MANAGER TEST")
|
|
print("="*60)
|
|
|
|
# Reset singleton
|
|
ClipboardManager._instance = None
|
|
manager = get_clipboard_manager()
|
|
|
|
print(f"\n1. Clipboard available: {manager.is_available()}")
|
|
|
|
# Copy test
|
|
print("\n2. Copying text to clipboard...")
|
|
manager.copy("Hello from EU-Utility!")
|
|
print(" Copied: 'Hello from EU-Utility!'")
|
|
|
|
# Paste test
|
|
print("\n3. Reading from clipboard...")
|
|
pasted = manager.paste()
|
|
print(f" Pasted: '{pasted}'")
|
|
|
|
# History test
|
|
print("\n4. Adding more entries...")
|
|
manager.copy("Coordinates: 100, 200", source="test")
|
|
manager.copy("GPS: 45.5231, -122.6765", source="test")
|
|
manager.copy("Position: x=50, y=100", source="test")
|
|
|
|
print("\n5. History (last 5):")
|
|
for i, entry in enumerate(manager.get_history(limit=5), 1):
|
|
print(f" {i}. [{entry.source}] {entry.content[:40]}")
|
|
|
|
print("\n6. Stats:")
|
|
stats = manager.get_stats()
|
|
for key, value in stats.items():
|
|
print(f" {key}: {value}")
|
|
|
|
print("\n" + "="*60)
|
|
print("TEST COMPLETE")
|
|
print("="*60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_manual_test()
|