Files
ElyOffice/crates/ely-lok/src/client/tests/workbook.rs
T

277 lines
11 KiB
Rust

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<String> {
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#"<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
<office:body><office:spreadsheet><table:table table:name="G">
<table:table-row><table:table-cell office:value-type="float" office:value="1"><text:p>1</text:p></table:table-cell><table:table-cell><text:p>b</text:p></table:table-cell></table:table-row>
<table:table-row><table:table-cell office:value-type="float" office:value="2"><text:p>2</text:p></table:table-cell></table:table-row>
</table:table></office:spreadsheet></office:body>
</office:document>"#;
#[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::<Vec<_>>(),
[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("<text:p>1 b</text:p>"), "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::<Vec<_>>();
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::<Vec<_>>();
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");
}