From b0f55326d243b3fd067c459f7c1415edef6b034a Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 23 Aug 2026 15:27:44 +0800 Subject: [PATCH] test(machine): hold the machine file readable by the build that has to read it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `machine.json` is a user's whole layout across sessions, and failing to parse it is not a degraded read: `load_machine` quarantines the file and starts empty, so every workspace they had is gone from the app. Swept it the way the protocol was swept — remove a `#[serde(default)]`, run the suite. Eleven of the twenty-seven are load-bearing, because serde fills a missing `Option` itself but a missing `Vec`, `String`, number or `bool` fails the document. Seven of those eleven were held by nothing: the workspace list, a workspace's tabs, a pane record's title and live flag, an attachment's host and time, and the cached appearance. Pinned as the shapes older builds actually wrote — a document with no workspaces key, a workspace before it had tabs, a pane record that was only an id — and re-swept afterwards: none of the eleven can lose its default now without failing. `Config` and `Session` needed nothing: both carry `#[serde(default)]` at the struct level, and three tests already fail if `Config` loses it. --- crates/tty7-core/src/core/machine.rs | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/tty7-core/src/core/machine.rs b/crates/tty7-core/src/core/machine.rs index 186c3112..037d9020 100644 --- a/crates/tty7-core/src/core/machine.rs +++ b/crates/tty7-core/src/core/machine.rs @@ -1730,6 +1730,56 @@ mod tests { /// handing every other reader something one of its readers had already /// decided was not drawable. /// + /// A `machine.json` an older build wrote still loads. + /// + /// This file is the whole of a user's layout across sessions, and a parse + /// failure is not a degraded read — `load_machine` quarantines the file and + /// starts empty, so every workspace they had is gone from the app. That + /// makes each `#[serde(default)]` on a non-`Option` field here + /// load-bearing: serde fills a missing `Option` on its own, but a missing + /// `Vec`, `String`, number or `bool` fails the document. + /// + /// Written as the shapes older builds actually wrote — a machine with no + /// workspaces key at all, a workspace before it had tabs, a pane record + /// that was only an id, an attachment before it named a host. Removing any + /// of the defaults involved fails here; seven of them were held by nothing. + #[test] + fn a_machine_file_from_an_older_build_still_loads() { + let m: Machine = serde_json::from_str("{}").expect("an empty document is an empty machine"); + assert!(m.workspaces.is_empty() && m.panes.is_empty()); + + let ws: Workspace = + serde_json::from_str(r#"{"id":"019622ff-0000-7000-8000-000000000001"}"#) + .expect("a workspace that predates tabs still loads"); + assert!(ws.tabs.is_empty()); + assert_eq!(ws.active_tab, None); + + let rec: PaneRecord = + serde_json::from_str(r#"{"id":7}"#).expect("a pane record that was only an id"); + assert_eq!(rec.id, 7); + assert_eq!(rec.title, ""); + assert!(!rec.live); + + let ap: Appearance = + serde_json::from_str("{}").expect("an appearance from before it carried a theme"); + assert!(!ap.dark); + + let at: Attachment = + serde_json::from_str("{}").expect("an attachment from before it named a host"); + assert_eq!(at.hostname, ""); + assert_eq!(at.since, 0); + + // And the whole thing nested, which is how it actually arrives. + let whole: Machine = serde_json::from_str( + r#"{"workspaces":[{"id":"019622ff-0000-7000-8000-000000000002", + "tabs":[{"root":{"Leaf":{"pane":1}}}]}],"panes":[{"id":1}]}"#, + ) + .expect("a document written before the later fields existed"); + assert_eq!(whole.workspaces.len(), 1); + assert_eq!(whole.workspaces[0].tabs.len(), 1); + assert_eq!(whole.panes[0].id, 1); + } + /// All three name-setting paths share this funnel, so the check covers /// creating a workspace, renaming one, and renaming a tab together. #[test]