Possible bug fix for clipboard sync and handling.
This commit is contained in:
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)
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Reference in new issue
Block a user