""" 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()