Possible bug fix for clipboard sync and handling.

This commit is contained in:
Storm Dragon committed 2026-10-10 18:08:50 -04:00
1 parent 1638f66a93
commit c018f5fd29
10 files changed
+225 -26

No files matched your search

@@ -28,12 +28,15 @@ def test_clipboard_import_changes_history_only_after_result_event():
"BackgroundTaskManager": task_manager,
"MemoryManager": memory_manager,
"OutputManager": Mock(),
"SettingsManager": Mock(get_setting=Mock(return_value=":2")),
"ClipboardSyncManager": Mock(),
}
}
command = import_clipboard_from_x.command()
command.initialize(environment)
command.run()
assert task_manager.args == (":2",)
memory_manager.add_value_to_first_index.assert_not_called()
task_manager.callback(
@@ -42,6 +45,8 @@ def test_clipboard_import_changes_history_only_after_result_event():
memory_manager.add_value_to_first_index.assert_called_once_with(
"clipboardHistory", "copied text"
)
sync_manager = environment["runtime"]["ClipboardSyncManager"]
sync_manager.mark_imported_from_x.assert_called_once_with("copied text")
def test_clipboard_export_updates_sync_state_only_after_result_event():
@@ -58,15 +63,29 @@ def test_clipboard_export_updates_sync_state_only_after_result_event():
"DebugManager": Mock(),
"MemoryManager": memory_manager,
"OutputManager": Mock(),
"SettingsManager": Mock(get_setting=Mock(return_value=":2")),
}
}
command = export_clipboard_to_x.command()
command.initialize(environment)
command.run()
assert task_manager.args == ("copied text", ":2")
sync_manager.mark_written_to_x.assert_not_called()
task_manager.callback(
{"task_id": 7, "succeeded": True, "value": True}
)
sync_manager.mark_written_to_x.assert_called_once_with("copied text")
def test_manual_clipboard_workers_use_configured_display(monkeypatch):
read_text = Mock(return_value="text")
write_text = Mock(return_value=True)
monkeypatch.setattr(import_clipboard_from_x.x_clipboard, "read_text", read_text)
monkeypatch.setattr(export_clipboard_to_x.x_clipboard, "write_text", write_text)
assert import_clipboard_from_x.read_clipboard(":2") == "text"
assert export_clipboard_to_x.write_clipboard("text", ":2") is True
read_text.assert_called_once_with(":2", scan_displays=True)
write_text.assert_called_once_with("text", ":2", scan_displays=True)
+96
View File
@@ -355,3 +355,99 @@ def test_sync_worker_returns_changes_without_manager_access(monkeypatch):
assert result["import_text"] == "from x"
assert result["last_observed_x"] == "from x"
def run_sync_task(manager):
task_manager = manager.env['runtime']['BackgroundTaskManager']
manager.handle_sync_event()
function, callback, *args = task_manager.submit_task.call_args.args
callback({'task_id': 12, 'succeeded': True, 'value': function(*args)})
@pytest.mark.unit
@pytest.mark.parametrize('failure', [False, RuntimeError('temporary failure')])
def test_runtime_sync_retries_failed_exports(monkeypatch, failure):
manager = ClipboardSyncManager()
manager.env = build_env(fenrir_text='new copy')
manager.env['runtime']['BackgroundTaskManager'] = Mock(
submit_task=Mock(return_value=12),
)
manager.running = True
manager.display = ':1'
manager.last_observed_fenrir = 'previous copy'
manager.last_observed_x = 'previous copy'
write_text = Mock(side_effect=[failure, True])
monkeypatch.setattr(
'fenrirscreenreader.core.clipboardSyncManager.x_clipboard.read_text',
Mock(return_value='previous copy'),
)
monkeypatch.setattr(
'fenrirscreenreader.core.clipboardSyncManager.x_clipboard.write_text',
write_text,
)
run_sync_task(manager)
assert manager.last_observed_fenrir == 'previous copy'
run_sync_task(manager)
assert write_text.call_count == 2
assert manager.last_observed_fenrir == 'new copy'
assert manager.last_observed_x == 'new copy'
manager.env['runtime']['MemoryManager'].add_value_to_first_index.assert_not_called()
@pytest.mark.unit
def test_poll_once_retries_failed_export(monkeypatch):
manager = ClipboardSyncManager()
manager.env = build_env(fenrir_text='new copy')
manager.display = ':1'
write_text = Mock(side_effect=[False, True])
monkeypatch.setattr(
'fenrirscreenreader.core.clipboardSyncManager.x_clipboard.read_text',
Mock(return_value=None),
)
monkeypatch.setattr(
'fenrirscreenreader.core.clipboardSyncManager.x_clipboard.write_text',
write_text,
)
manager.poll_once()
assert manager.last_observed_fenrir is None
manager.poll_once()
assert write_text.call_count == 2
assert manager.last_written_to_x == 'new copy'
@pytest.mark.unit
@pytest.mark.parametrize('manual_action', ['export', 'import'])
def test_runtime_sync_discards_result_superseded_by_manual_transfer(
manual_action,
):
manager = ClipboardSyncManager()
manager.env = build_env(fenrir_text='current copy')
task_manager = Mock(submit_task=Mock(return_value=12))
manager.env['runtime']['BackgroundTaskManager'] = task_manager
manager.running = True
manager.last_observed_fenrir = 'current copy'
manager.last_observed_x = 'current copy'
manager.handle_sync_event()
if manual_action == 'export':
manager.mark_written_to_x('current copy')
else:
manager.mark_imported_from_x('current copy')
callback = task_manager.submit_task.call_args.args[1]
callback({
'task_id': 12, 'succeeded': True,
'value': {
'import_text': 'old x copy',
'last_imported_from_x': 'old x copy',
'last_observed_fenrir': 'old x copy',
'last_observed_x': 'old x copy',
},
})
manager.env['runtime']['MemoryManager'].add_value_to_first_index.assert_not_called()
assert manager.last_observed_x == 'current copy'
assert manager._task_id is None
assert not manager._sync_event_pending.is_set()
+52
View File
@@ -107,3 +107,55 @@ def test_write_text_propagates_clipboard_timeout(monkeypatch):
with pytest.raises(subprocess.TimeoutExpired):
x_clipboard.write_text("clipboard text", ":3")
@pytest.mark.unit
@pytest.mark.parametrize("operation", ["read", "write"])
def test_current_display_failure_does_not_scan_other_sessions(
monkeypatch, operation,
):
monkeypatch.setenv("DISPLAY", ":0")
monkeypatch.setattr(x_clipboard.shutil, "which", command_exists)
run_command = Mock(return_value=subprocess.CompletedProcess(
["xclip"], 1, stdout=b"", stderr=b"Can't open display: :0",
))
monkeypatch.setattr(x_clipboard.subprocess, "run", run_command)
with pytest.raises(RuntimeError):
if operation == "read":
x_clipboard.read_text(scan_displays=True)
else:
x_clipboard.write_text("text", scan_displays=True)
assert run_command.call_count == 1
assert run_command.call_args.kwargs["env"]["DISPLAY"] == ":0"
@pytest.mark.unit
def test_empty_clipboard_stops_display_scan(monkeypatch):
monkeypatch.delenv("DISPLAY", raising=False)
monkeypatch.setattr(x_clipboard.shutil, "which", command_exists)
run_command = Mock(return_value=subprocess.CompletedProcess(
["xclip"], 0, stdout=b"", stderr=b"",
))
monkeypatch.setattr(x_clipboard.subprocess, "run", run_command)
assert x_clipboard.read_text(scan_displays=True) is None
assert run_command.call_count == 1
@pytest.mark.unit
@pytest.mark.parametrize("target", ["STRING", "UTF8_STRING"])
def test_unavailable_text_selection_is_an_empty_clipboard(
monkeypatch, target,
):
monkeypatch.setenv("DISPLAY", ":0")
monkeypatch.setattr(x_clipboard.shutil, "which", command_exists)
run_command = Mock(return_value=subprocess.CompletedProcess(
["xclip"], 1, stdout=b"",
stderr=f"Error: target {target} not available\n".encode(),
))
monkeypatch.setattr(x_clipboard.subprocess, "run", run_command)
assert x_clipboard.read_text(scan_displays=True) is None
assert run_command.call_count == 1