Merge pull request #1346 from storytold/codex/egui-037-fig-followup-20261025

Keep synthetic Copy link commands inside the clipboard fence
This commit is contained in:
BFlat
2026-10-26 09:04:40 +07:00
committed by GitHub
3 changed files with 37 additions and 5 deletions
+3 -4
View File
@@ -129,12 +129,11 @@ fn two_files_are_hosted_and_joined_independently() {
// Each joined file opens in front when its welcome arrives, and the two joins race: the file
// whose welcome came last is in front.
assert_eq!(guest.ds().unwrap().uid, guest.docs[2].uid, "the file that arrived last is in front");
guest.execute("doc.switch", &json!({"index": guest.docs.iter().position(|d| d.uid == gb).unwrap()})).unwrap();
// An edit in A reaches the guest's copy of A only, while B is in front at the guest.
// Concurrent joins may complete in either order. Put B in front explicitly so the
// following assertions exercise A's background session independent of network timing.
// Concurrent joins may complete in either order. Put B in front explicitly so the following
// assertions exercise A's background session independent of network timing.
guest.execute("doc.switch", &json!({"index": guest.docs.iter().position(|d| d.uid == gb).unwrap()})).unwrap();
assert_eq!(guest.ds().unwrap().uid, gb, "B is in front while A receives edits");
// An edit in A reaches the guest's copy of A only, while B is in front at the guest.
let in_a = rect(&mut host);
host.execute("doc.switch", &json!({"index": 1})).unwrap();
let in_b = rect(&mut host);
@@ -131,6 +131,37 @@ fn a_copy_command_invoked_for_a_synthetic_click_still_keeps_the_clipboard() {
assert_eq!(w.writes(), (1, 0));
}
/// Asserts a real `ui.menu.invoke` of Copy link is fenced from the system clipboard, while the
/// same menu item invoked by the user writes the selected layer's link.
#[test]
fn a_synthetic_ui_menu_invoke_copy_link_keeps_the_clipboard_then_a_user_copy_writes_the_link() {
let mut w = Win::new();
let selected = *w.app.session.selection().first().expect("a selected layer");
let link = format!("figcraft://node/{}", selected.0);
let (tx, rx) = std::sync::mpsc::channel();
w.app.control_queue.push(ControlRequest {
id: json!(1),
method: "ui.menu.invoke".into(),
params: json!({"id": "edit.copyLink"}),
reply: tx,
state: Default::default(),
});
w.frame(vec![]);
let reply = rx.try_recv().expect("the menu invocation is acknowledged");
assert_eq!(reply.id, json!(1));
assert!(reply.result.is_ok(), "the menu item ran: {:?}", reply.result);
assert_eq!(w.writes(), (0, 0), "a synthetic menu invocation never replaces the user's clipboard");
assert_eq!(w.app.ui.toast.as_ref().map(|t| t.0.as_str()), Some("Link copied"));
assert!(!w.app.control_synthetic, "the fence ends with the frame");
crate::menus::invoke(&mut w.app, "edit.copyLink");
assert_eq!(w.writes(), (1, 0), "the user's invocation writes exactly one text clipboard entry");
let recorded = w.recorded.lock().unwrap();
assert_eq!(recorded.text, vec![link.clone()]);
assert_eq!(recorded.last_text.as_deref(), Some(link.as_str()));
}
/// Return the characters of a text node in the test window.
fn edit_text(w: &Win, id: NodeId) -> String {
match &w.app.session.doc().unwrap().node(id).unwrap().kind {
+3 -1
View File
@@ -241,8 +241,10 @@ pub fn invoke(app: &mut FigApp, id: &str) {
app.toast(tr("Copied as text"));
}
"edit.copyLink" => {
// Only a selected layer has a link, and the copy goes through the control-channel fence
// (`clipboard_set_text`): a synthetic gesture never replaces the user's clipboard.
if let Some(id) = app.session.selection().first() {
app.services.clipboard_set_text(&format!("figcraft://node/{}", id.0));
app.clipboard_set_text(&format!("figcraft://node/{}", id.0));
app.toast(tr("Link copied"));
}
}