Files
figcraft/crates/doc/src/guides_tests.rs
T
Filip StrajnarandClaude Opus 5.5 652bf35b19 M1: page ruler guides survive save and reopen (#1476)
A page kept its ruler guides in `NodeKind::Page`, and since frame guides
(#70) every layer also has its own `guides` field. Both are written as
`guides`, and on reading the layer's own field takes the key, so:

- a reopened page's guides landed in the layer field and were not drawn;
- saving that page wrote `guides` twice (the page kind's list, always
  written, then the layer's), and the file no longer opened ("duplicate
  field `guides`").

Every layer, pages included, now keeps its guides in the one layer field;
the key in the file is unchanged, so existing files open with their guides.
The field reads every `guides` list a layer has, so files already saved
with the key twice open again with both lists, in file order. This also
covers collab `Create` payloads from older peers.

Regressions: JSON round trips of page and frame guides, a page with two
lists, a bad list still refused (doc); save, reopen, save, reopen through
`.figcraft` and a document.json with the key twice (format). Checked in the
app over the control channel: guides drawn after two save/reopen cycles, and
a file with the key twice opens with all its guides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6Dyzof21BCrUsByhx2w
2026-10-28 22:47:24 +01:00

62 lines
2.8 KiB
Rust

//! Ruler guides survive a save and reopen (#1476).
use crate::{Document, Guide, Node, NodeKind};
/// A guide: vertical (at an x offset) or horizontal (at a y offset).
fn guide(vertical: bool, offset: f64) -> Guide {
Guide { vertical, offset }
}
/// A page's guides are written once and read back onto the page.
#[test]
fn page_guides_round_trip_through_json() {
let mut d = Document::new("a");
let page = d.first_page().unwrap();
d.node_mut(page).unwrap().guides_mut().push(guide(true, 120.0));
let json = serde_json::to_string(d.node(page).unwrap()).unwrap();
assert_eq!(json.matches("\"guides\"").count(), 1, "{json}");
let back: Node = serde_json::from_str(&json).unwrap();
assert_eq!(back.guides(), &[guide(true, 120.0)]);
assert_eq!(back, *d.node(page).unwrap());
}
/// A frame's guides round trip too, and a layer without guides writes no `guides` key.
#[test]
fn frame_guides_round_trip_and_none_are_left_out() {
let mut d = Document::new("a");
let page = d.first_page().unwrap();
let frame = d.create(page, NodeKind::Frame, "F", 0.0, 0.0, 100.0, 100.0).unwrap();
for id in [page, frame] {
let json = serde_json::to_value(d.node(id).unwrap()).unwrap();
assert!(json.get("guides").is_none(), "{json}");
}
d.node_mut(frame).unwrap().guides_mut().push(guide(false, 40.0));
let json = serde_json::to_string(d.node(frame).unwrap()).unwrap();
assert_eq!(json.matches("\"guides\"").count(), 1, "{json}");
let back: Node = serde_json::from_str(&json).unwrap();
assert_eq!(back, *d.node(frame).unwrap());
}
/// A page saved with two `guides` keys reads both lists, in file order, and writes them once.
#[test]
fn a_page_written_with_two_guides_lists_opens_with_both() {
// How a page was saved after its file had been reopened: the page kind's list (guides added
// since), then the layer's (the ones the file had).
let json = r#"{"id": 1, "name": "Page 1", "type": "page", "guides": [{"vertical": true, "offset": 5.0}],
"width": 0.0, "height": 0.0, "guides": [{"vertical": false, "offset": 7.0}]}"#;
let n: Node = serde_json::from_str(json).unwrap();
assert!(matches!(n.kind, NodeKind::Page { .. }));
assert_eq!(n.guides(), &[guide(true, 5.0), guide(false, 7.0)]);
let again: Node = serde_json::from_str(&serde_json::to_string(&n).unwrap()).unwrap();
assert_eq!(again.guides(), n.guides());
}
/// A `guides` value that isn't a list of guides is still refused.
#[test]
fn a_bad_guides_list_is_still_an_error() {
for bad in [r#"3"#, r#"[{"vertical": "no"}]"#, r#"{"offset": 1}"#] {
let json = format!(r#"{{"id": 1, "name": "F", "type": "frame", "width": 1.0, "height": 1.0, "guides": {bad}}}"#);
assert!(serde_json::from_str::<Node>(&json).is_err(), "{bad}");
}
}