diff --git a/apps/gridcraft/src/main.rs b/apps/gridcraft/src/main.rs index 5366bdb44..9357d0796 100644 --- a/apps/gridcraft/src/main.rs +++ b/apps/gridcraft/src/main.rs @@ -311,15 +311,16 @@ fn main() -> eframe::Result<()> { { log::warn!("sample {s}: {e}"); } - if session.documents().is_empty() { - session.new_workbook(); - } let mut app = SheetApp::new(session, services()); app.watch_desktop_theme(); app.start_update_check(); - app.after_engine(); // Show warnings from the sample. let control_port = control_port; load_prefs(&mut app); + // Stamp the startup workbook's author from the persisted user name. + if app.session.documents().is_empty() { + app.session.new_workbook(); + } + app.after_engine(); // Show warnings from the sample. // Files named on the command line (a double-click on Windows and Linux) open as File › Open // opens them: a password-protected one asks for its password, and errors are shown, not // only logged. Each replaces the untouched blank Book1, as a file opened from Finder does. diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index d23e6fbd3..3ae4ac95a 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -587,6 +587,7 @@ impl Session { pub fn new_workbook(&mut self) -> usize { let n = (1..).find(|n| !self.docs.iter().any(|d| d.title == format!("Book{n}") && d.path.is_none())).unwrap_or(1); let mut wb = Workbook::new(); + wb.props.author = self.prefs.user_name.clone(); for _ in 1..self.prefs.sheets_in_new_workbook.clamp(1, 255) { let name = wb.next_sheet_name(); wb.sheets.push(Arc::new(gridcraft_model::Sheet::new(name))); diff --git a/crates/engine/tests/it/main.rs b/crates/engine/tests/it/main.rs index 28d0831e3..20898e152 100644 --- a/crates/engine/tests/it/main.rs +++ b/crates/engine/tests/it/main.rs @@ -29,4 +29,5 @@ mod sparse_clear_invalidation; mod stored_list_shape; mod table_resize_overlap; mod table_total_row_last_row; +mod workbook_author; mod wrap_row_height; diff --git a/crates/engine/tests/it/workbook_author.rs b/crates/engine/tests/it/workbook_author.rs new file mode 100644 index 000000000..7af8b5d68 --- /dev/null +++ b/crates/engine/tests/it/workbook_author.rs @@ -0,0 +1,50 @@ +//! Regression tests for the default author of newly created workbooks. + +use gridcraft_engine::{DocState, Session, Trust, io, model::Workbook}; + +#[test] +fn new_workbook_stamps_the_configured_user_name_at_creation() { + let mut session = Session::new(); + + session.prefs.user_name = "Ada Lovelace".into(); + let first = session.new_workbook(); + + assert_eq!(session.documents()[first].wb.props.author, "Ada Lovelace"); + + // Changing the preference must not rewrite an existing workbook. + session.prefs.user_name = "Grace Hopper".into(); + + assert_eq!(session.documents()[first].wb.props.author, "Ada Lovelace"); + + // A new workbook uses the preference as it exists at creation. + let second = session.new_workbook(); + + assert_eq!(session.documents()[second].wb.props.author, "Grace Hopper"); + + assert_eq!(session.documents()[first].wb.props.author, "Ada Lovelace"); +} + +#[test] +fn opening_and_resaving_preserves_the_existing_workbook_author() { + // An existing XLSX has its own author metadata. + let mut source = Workbook::new(); + source.props.author = "Original Spreadsheet Author".into(); + + let bytes = io::save_bytes(&source, "source.xlsx").unwrap(); + let (opened, _) = io::open_bytes("source.xlsx", &bytes).unwrap(); + + let mut session = Session::new(); + session.prefs.user_name = "Current GridCraft User".into(); + + session.add_document(DocState::new(opened, None, "source".into(), Trust::FromFile)); + + // Opening an existing workbook must not replace its author. + assert_eq!(session.doc().unwrap().wb.props.author, "Original Spreadsheet Author"); + + // The original author must survive another XLSX save and reopen. + let saved = io::save_bytes(&session.doc().unwrap().wb, "saved.xlsx").unwrap(); + + let (reopened, _) = io::open_bytes("saved.xlsx", &saved).unwrap(); + + assert_eq!(reopened.props.author, "Original Spreadsheet Author"); +}