mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-27 03:46:50 -06:00
A branch's version history starts with "Branch created" (the branch as it began) and gets "Updated from main" each time it takes main's changes (the branch right after, on its tab or closed). They are versions like any other: viewed, restored, saved with the file, never pruned as autosaves, and they stay out of the main file's history.
127 lines
6.5 KiB
Rust
127 lines
6.5 KiB
Rust
//! A branch's own version history: versions saved on a branch tab go into the branch (not the
|
|
//! main file's history), are viewed and restored there, are saved in the file with the branch and
|
|
//! stay with it when it is merged or archived; merging adds a version to the main file's history.
|
|
//! Also fills merging item by item through real commands.
|
|
|
|
use figcraft_doc::{NodeId, NodeKind};
|
|
use serde_json::{Value, json};
|
|
|
|
use crate::Session;
|
|
use crate::collab_tests::base;
|
|
|
|
fn ok(s: &mut Session, cmd: &str, p: Value) -> Value {
|
|
s.execute(cmd, &p).unwrap_or_else(|e| panic!("{cmd} {p}: {e}"))
|
|
}
|
|
|
|
fn names(s: &mut Session) -> Vec<String> {
|
|
ok(s, "version.list", json!({})).as_array().unwrap().iter().map(|v| v["name"].as_str().unwrap().to_string()).collect()
|
|
}
|
|
|
|
fn layer_name(s: &Session, id: NodeId) -> String {
|
|
s.doc().unwrap().node(id).unwrap().name.clone()
|
|
}
|
|
|
|
/// The base file with one version saved on main and a branch "Explore" shown: (session, branch
|
|
/// id, a rectangle).
|
|
fn branched() -> (Session, String, NodeId) {
|
|
let mut s = Session::with_document(base());
|
|
let r = s.doc().unwrap().nodes().find(|n| matches!(n.kind, NodeKind::Rectangle)).unwrap().id;
|
|
ok(&mut s, "file.saveVersion", json!({"name": "Main v1"}));
|
|
let id = ok(&mut s, "branch.create", json!({"name": "Explore"}))["id"].as_str().unwrap().to_string();
|
|
(s, id, r)
|
|
}
|
|
|
|
#[test]
|
|
fn versions_saved_on_a_branch_are_the_branchs_own() {
|
|
let (mut s, _, r) = branched();
|
|
let original = layer_name(&s, r);
|
|
// A new branch's history starts with its creation: the main file's versions stay with it.
|
|
assert_eq!(names(&mut s), ["Branch created"]);
|
|
ok(&mut s, "node.set", json!({"id": r, "props": {"name": "Before"}}));
|
|
let v = ok(&mut s, "file.saveVersion", json!({"name": "Branch v1"}))["id"].clone();
|
|
ok(&mut s, "node.set", json!({"id": r, "props": {"name": "After"}}));
|
|
assert_eq!(names(&mut s), ["Branch created", "Branch v1"]);
|
|
// Viewing a branch version shows it read-only; going back shows the branch as it is.
|
|
ok(&mut s, "version.preview", json!({"id": v}));
|
|
assert_eq!(layer_name(&s, r), "Before");
|
|
assert!(s.execute("node.set", &json!({"id": r, "props": {"name": "No"}})).is_err());
|
|
ok(&mut s, "version.exitPreview", json!({}));
|
|
assert_eq!(layer_name(&s, r), "After");
|
|
// Restoring is one undo step on the branch.
|
|
ok(&mut s, "version.restore", json!({"id": v}));
|
|
assert_eq!(layer_name(&s, r), "Before");
|
|
ok(&mut s, "edit.undo", json!({}));
|
|
assert_eq!(layer_name(&s, r), "After");
|
|
ok(&mut s, "edit.redo", json!({}));
|
|
// Renaming works on the branch's history; main's history is untouched.
|
|
ok(&mut s, "version.rename", json!({"id": v, "name": "Branch v1 (renamed)"}));
|
|
ok(&mut s, "doc.switch", json!({"index": 0}));
|
|
assert_eq!(names(&mut s), ["Main v1"]);
|
|
assert_eq!(layer_name(&s, r), original, "restoring on the branch leaves main alone");
|
|
}
|
|
|
|
#[test]
|
|
fn branch_versions_save_with_the_file_and_survive_merging() {
|
|
let dir = std::env::temp_dir().join(format!("figcraft-branch-versions-{}", std::process::id()));
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let path = dir.join("file.figcraft").to_string_lossy().to_string();
|
|
let (mut s, id, r) = branched();
|
|
ok(&mut s, "node.set", json!({"id": r, "props": {"name": "On the branch"}}));
|
|
ok(&mut s, "file.saveVersion", json!({"name": "Branch v1"}));
|
|
// An autosave on a branch tab goes into the branch's history too.
|
|
ok(&mut s, "node.set", json!({"id": r, "props": {"name": "On the branch, later"}}));
|
|
assert_eq!(ok(&mut s, "version.autosave", json!({}))["saved"], true);
|
|
ok(&mut s, "doc.switch", json!({"index": 0}));
|
|
ok(&mut s, "file.saveAs", json!({"path": path}));
|
|
let mut t = Session::empty();
|
|
ok(&mut t, "file.open", json!({"path": path}));
|
|
assert_eq!(names(&mut t), ["Main v1"]);
|
|
ok(&mut t, "branch.open", json!({"id": id}));
|
|
assert_eq!(names(&mut t), ["Branch created", "Branch v1", "Autosave"]);
|
|
// Merging adds a version to the main file's history; the branch keeps its own.
|
|
ok(&mut t, "branch.merge", json!({}));
|
|
assert_eq!(names(&mut t), ["Main v1", "Autosave", "Merged “Explore”"]);
|
|
assert_eq!(layer_name(&t, r), "On the branch, later");
|
|
ok(&mut t, "branch.open", json!({"id": id}));
|
|
assert_eq!(names(&mut t), ["Branch created", "Branch v1", "Autosave"]);
|
|
std::fs::remove_dir_all(&dir).ok();
|
|
}
|
|
|
|
#[test]
|
|
fn archiving_keeps_the_branch_history() {
|
|
let (mut s, id, _) = branched();
|
|
ok(&mut s, "file.saveVersion", json!({"name": "Branch v1"}));
|
|
ok(&mut s, "branch.archive", json!({}));
|
|
assert_eq!(names(&mut s), ["Main v1"]);
|
|
ok(&mut s, "branch.open", json!({"id": id}));
|
|
assert_eq!(names(&mut s), ["Branch created", "Branch v1"]);
|
|
}
|
|
|
|
#[test]
|
|
fn different_fills_of_a_layer_merge_without_a_conflict() {
|
|
// Main gives a rectangle a second fill before branching; the branch recolours the first and
|
|
// main the second: no conflict, both kept. Recolouring the same fill both ways conflicts.
|
|
let mut s = Session::with_document(base());
|
|
let r = s.doc().unwrap().nodes().find(|n| matches!(n.kind, NodeKind::Rectangle)).unwrap().id;
|
|
ok(&mut s, "fill.add", json!({"ids": [r]}));
|
|
let id = ok(&mut s, "branch.create", json!({}))["id"].as_str().unwrap().to_string();
|
|
ok(&mut s, "fill.set", json!({"ids": [r], "index": 0, "color": "#FF0000"}));
|
|
ok(&mut s, "doc.switch", json!({"index": 0}));
|
|
ok(&mut s, "fill.set", json!({"ids": [r], "index": 1, "color": "#0000FF"}));
|
|
assert_eq!(ok(&mut s, "branch.review", json!({"id": id}))["conflicts"], json!([]));
|
|
ok(&mut s, "branch.merge", json!({"id": id}));
|
|
let fills = serde_json::to_string(&s.doc().unwrap().node(r).unwrap().fills).unwrap();
|
|
let mut t = Session::with_document(s.doc().unwrap().clone());
|
|
ok(&mut t, "fill.set", json!({"ids": [r], "index": 0, "color": "#FF0000"}));
|
|
ok(&mut t, "fill.set", json!({"ids": [r], "index": 1, "color": "#0000FF"}));
|
|
assert_eq!(fills, serde_json::to_string(&t.doc().unwrap().node(r).unwrap().fills).unwrap(), "both recolours kept");
|
|
|
|
let id = ok(&mut s, "branch.create", json!({}))["id"].as_str().unwrap().to_string();
|
|
ok(&mut s, "fill.set", json!({"ids": [r], "index": 1, "color": "#00FF00"}));
|
|
ok(&mut s, "doc.switch", json!({"index": 0}));
|
|
ok(&mut s, "fill.set", json!({"ids": [r], "index": 1, "color": "#FFFF00"}));
|
|
let conflicts = ok(&mut s, "branch.review", json!({"id": id}))["conflicts"].clone();
|
|
assert_eq!(conflicts.as_array().unwrap().len(), 1, "{conflicts}");
|
|
assert_eq!(conflicts[0]["key"], format!("{r}/fills"));
|
|
}
|