use super::sheet::{SHEETS, open_book}; use super::*; use crate::sheet; /// How long a command may take to report. const SETTLE: std::time::Duration = std::time::Duration::from_secs(5); fn names(worker: &mut Worker, doc: &Doc) -> Vec<(String, bool)> { doc.sheets(worker) .expect("the sheets") .list .into_iter() .map(|sheet| (sheet.name, sheet.visible)) .collect() } /// Runs `id` as an app does, at a fresh read. fn run(worker: &mut Worker, doc: &Doc, id: &str, values: &[&str]) { let at = worker.now(doc).expect("the state"); worker .run(&at, id, values) .unwrap_or_else(|error| panic!("{id} {values:?}: {error}")); } fn geometry(worker: &mut Worker, doc: &Doc) -> sheet::Geometry { sheet::geometry(&worker.values(doc, sheet::GEOMETRY).expect("the geometry")) .expect("the geometry parses") } #[test] fn sheets_insert_rename_move_hide_and_delete() { if install().is_none() { eprintln!("SKIP: no pinned LOK build"); return; } let (_dir, mut worker, doc) = open_book(SHEETS); let owned = |list: &[(&str, bool)]| -> Vec<(String, bool)> { list.iter() .map(|&(name, shown)| (name.to_string(), shown)) .collect() }; let refs = sheet::references( &worker .values(&doc, &sheet::references_query(0)) .expect("the references"), ) .expect("they parse"); assert_eq!((refs.formulas, refs.names), (1, 0), "U refers to S"); assert_eq!(refs.dynamic, 1, "INDIRECT on U is counted apart"); assert!( refs.first.contains("U"), "the first is on U: {}", refs.first ); run(&mut worker, &doc, "sheet.insert", &["New", "2"]); run(&mut worker, &doc, "sheet.rename", &["Data", "2"]); assert_eq!( names(&mut worker, &doc), owned(&[("S", true), ("Data", true), ("T", false), ("U", true)]) ); doc.show_sheet(&mut worker, 1).expect("Data shows"); run(&mut worker, &doc, "sheet.move", &["5", "false", "true"]); run(&mut worker, &doc, "sheet.move", &["5", "true", "true"]); let list = names(&mut worker, &doc); assert_eq!(list[..3], owned(&[("S", true), ("T", false), ("U", true)])); assert_eq!(list[3].0, "Data"); assert!(list[4].0.starts_with("Data"), "a copy: {}", list[4].0); doc.show_sheet(&mut worker, 0).expect("S shows"); run(&mut worker, &doc, "sheet.move", &["2", "false", "true"]); let order = |worker: &mut Worker| -> Vec { names(worker, &doc) .into_iter() .map(|(name, _)| name) .collect() }; assert_eq!( order(&mut worker)[..3], ["T", "S", "U"], "Index i+2 moves right" ); run(&mut worker, &doc, "sheet.move", &["1", "false", "true"]); assert_eq!( order(&mut worker)[..3], ["S", "T", "U"], "Index i moves left" ); run(&mut worker, &doc, "sheet.hide", &["2"]); run(&mut worker, &doc, "sheet.show", &["T"]); let list = names(&mut worker, &doc); assert_eq!(list[1], ("T".to_string(), true), "T shows"); assert_eq!(list[2], ("U".to_string(), false), "U hides"); run(&mut worker, &doc, "sheet.delete", &["1"]); assert_eq!(names(&mut worker, &doc)[0].0, "T", "S is gone"); } /// A 1, 2 column to fill, and cells to merge. const GRID: &str = r#" 1b 2 "#; #[test] fn ranges_fill_size_hide_merge_and_take_formulas() { if install().is_none() { eprintln!("SKIP: no pinned LOK build"); return; } let (dir, mut worker, doc) = open_book(GRID); run(&mut worker, &doc, "cell.goto", &["A1:A2"]); run(&mut worker, &doc, "fill.auto", &["A5"]); run(&mut worker, &doc, "cell.goto", &["C1"]); run(&mut worker, &doc, "cell.enter", &["=SUM(A1:A5)"]); run(&mut worker, &doc, "cell.goto", &["C1"]); let summed = poll_until(&mut worker, &doc, &Events::default(), SETTLE, |seen| { seen.cell.address == "C1" && seen.cell.formula == "=SUM(A1:A5)" }); assert_eq!(summed.cell.formula, "=SUM(A1:A5)"); run(&mut worker, &doc, "cell.goto", &["F1:H1"]); run(&mut worker, &doc, "columns.width_selected", &["3000"]); run(&mut worker, &doc, "columns.width", &["2540", "4"]); run(&mut worker, &doc, "cell.goto", &["E1"]); run(&mut worker, &doc, "columns.hide", &[]); let sizes = geometry(&mut worker, &doc); assert_eq!(sizes.columns.size(3), 1440, "D is 1 inch wide"); assert_eq!(sizes.columns.size(4), 0, "E hides"); assert_eq!( (5..8) .map(|column| sizes.columns.size(column)) .collect::>(), [1701, 1701, 1701], "F to H all take 3 cm" ); run(&mut worker, &doc, "select.column", &["1", "0"]); run(&mut worker, &doc, "select.row", &["2", "4096"]); run(&mut worker, &doc, "cell.goto", &["B2"]); run(&mut worker, &doc, "view.freeze", &[]); let frozen = poll_until(&mut worker, &doc, &Events::default(), SETTLE, |seen| { let state = |uno: &str| { seen.states .iter() .rev() .find(|(name, _)| name == uno) .map(|(_, state)| state.clone()) }; state(".uno:FreezePanesColumn").as_deref() == Some("1 / 0") && state(".uno:FreezePanesRow").as_deref() == Some("1 / 0") }); assert!( frozen .states .iter() .any(|(name, state)| name == ".uno:FreezePanesRow" && state == "1 / 0"), "B2 freezes one row and one column: {:?}", frozen.states ); run(&mut worker, &doc, "cell.goto", &["A1"]); run(&mut worker, &doc, "rows.insert_above", &[]); run(&mut worker, &doc, "cell.goto", &["A2:B2"]); assert_eq!( doc.selection_text(&mut worker) .expect("the text") .trim_end(), "1\tb", "two cells hold data" ); run(&mut worker, &doc, "cells.merge", &["true"]); let out = dir.path().join("out.fods"); doc.save_as(&mut worker, &crate::file_url(&out), "fods") .expect("the save"); let xml = std::fs::read_to_string(&out).expect("the save reads"); assert!( xml.contains("table:number-columns-spanned=\"2\""), "A2:B2 merges" ); assert!( xml.contains("of:=SUM([.A2:.A6])"), "the formula follows the inserted row" ); assert!( xml.contains("office:value=\"14\""), "the fill reaches 5; the merged text drops out of the sum" ); assert!(xml.contains("1 b"), "the contents move"); } #[test] fn two_workbooks_keep_their_own_sheets() { if install().is_none() { eprintln!("SKIP: no pinned LOK build"); return; } let (dir, mut worker, first) = open_book(SHEETS); first.show_sheet(&mut worker, 2).expect("U shows"); let grid = dir.path().join("grid.fods"); std::fs::write(&grid, GRID).expect("the grid writes"); let second = worker .load(&crate::file_url(&grid)) .expect("the second book loads"); first.show_sheet(&mut worker, 0).expect("S shows"); assert_eq!(first.sheets(&mut worker).expect("sheets").current, 0); assert_eq!(second.sheets(&mut worker).expect("sheets").current, 0); run(&mut worker, &first, "cell.goto", &["C1"]); run(&mut worker, &first, "cell.enter", &["123456"]); let out = dir.path().join("first.fods"); first .save_as(&mut worker, &crate::file_url(&out), "fods") .expect("the save"); let xml = std::fs::read_to_string(&out).expect("the save reads"); let on_s = &xml[..xml.find("table:name=\"T\"").expect("sheet T")]; assert!( on_s.contains("office:value=\"123456\""), "the entry lands on S" ); } #[test] fn a_sheet_by_number_needs_the_state_it_was_read_in() { if install().is_none() { eprintln!("SKIP: no pinned LOK build"); return; } let (_dir, mut worker, doc) = open_book(SHEETS); let read = worker.now(&doc).expect("the state"); worker.run(&read, "sheet.delete", &["1"]).expect("deletes S"); // Sheet 2 was T when read; it is U now. let stale = worker.run(&read, "sheet.rename", &["Renamed", "2"]).expect_err("an older state"); assert!(crate::is_stale(&stale), "{stale}"); let only = |worker: &mut Worker| names(worker, &doc).into_iter().map(|(name, _)| name).collect::>(); assert_eq!(only(&mut worker), ["T", "U"], "nothing was renamed"); run(&mut worker, &doc, "sheet.rename", &["Renamed", "2"]); assert_eq!(only(&mut worker), ["T", "Renamed"]); // A loaded handle never names a sheet after edits. assert!(worker.run(&doc, "sheet.delete", &["1"]).is_err()); } #[test] fn commands_from_one_read_run_together_and_hovering_changes_nothing() { if install().is_none() { eprintln!("SKIP: no pinned LOK build"); return; } let (_dir, mut worker, doc) = open_book(SHEETS); let read = worker.now(&doc).expect("the state"); let hover = crate::Mouse { kind: 2, x: 500, y: 500, count: 0, buttons: 0, modifiers: 0 }; worker.post_mouse(&read, hover).expect("hovers"); assert_eq!(worker.now(&doc).expect("the state"), read, "a hover is no change"); // Both numbers come from one read. let renames: [(&str, &[&str]); 2] = [("sheet.rename", &["One", "1"]), ("sheet.rename", &["Two", "2"])]; worker.run_all(&read, &renames).expect("renames both"); let only = |worker: &mut Worker| names(worker, &doc).into_iter().map(|(name, _)| name).collect::>(); assert_eq!(only(&mut worker), ["One", "Two", "U"]); let stale = worker.run(&read, "sheet.rename", &["Three", "3"]).expect_err("an older state"); assert!(crate::is_stale(&stale), "{stale}"); assert_eq!(only(&mut worker), ["One", "Two", "U"], "nothing else was renamed"); } #[test] fn a_header_act_after_a_click_is_not_stale() { if install().is_none() { eprintln!("SKIP: no pinned LOK build"); return; } let (_dir, mut worker, doc) = open_book(SHEETS); let read = worker.now(&doc).expect("the state"); // A click moves the revision; rows and columns stay. for kind in [0, 1] { let click = crate::Mouse { kind, x: 500, y: 500, count: 1, buttons: 1, modifiers: 0 }; worker.post_mouse(&read, click).expect("clicks"); } assert_ne!(worker.now(&doc).expect("the state"), read, "the click counts as a change"); worker.run(&read, "select.column", &["2", "0"]).expect("the column header still selects"); worker.run(&read, "columns.width", &["800", "3"]).expect("the column's width still sets"); }