UPDATE: Adds 'user model' API (#27)

* bump version for documentation
* Fixes wrong doc comment
* renames old APIs to be consistent
This commit is contained in:
Nicolás Hatcher Andrés
2024-04-03 22:41:15 +02:00
committed by GitHub
parent e9fc41541b
commit d445553d85
45 changed files with 3233 additions and 268 deletions

2
Cargo.lock generated
View File

@@ -204,7 +204,7 @@ dependencies = [
[[package]]
name = "ironcalc_base"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"chrono",
"chrono-tz",

View File

@@ -1,6 +1,6 @@
[package]
name = "ironcalc_base"
version = "0.1.2"
version = "0.1.3"
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
edition = "2021"
homepage = "https://www.ironcalc.com"

View File

@@ -1,4 +1,4 @@
use ironcalc_base::{model::Model, types::CellType};
use ironcalc_base::{types::CellType, Model};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut model = Model::new_empty("formulas-and-errors", "en", "UTC")?;

View File

@@ -1,4 +1,4 @@
use ironcalc_base::{cell::CellValue, model::Model};
use ironcalc_base::{cell::CellValue, Model};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut model = Model::new_empty("hello-world", "en", "UTC")?;

View File

@@ -77,13 +77,13 @@ impl Model {
let style = source_cell.get_style();
// FIXME: we need some user_input getter instead of get_text
let formula_or_value = self
.cell_formula(sheet, source_row, source_column)?
.get_cell_formula(sheet, source_row, source_column)?
.unwrap_or_else(|| source_cell.get_text(&self.workbook.shared_strings, &self.language));
self.set_user_input(sheet, target_row, target_column, formula_or_value);
self.workbook
.worksheet_mut(sheet)?
.set_cell_style(target_row, target_column, style);
self.delete_cell(sheet, source_row, source_column)?;
self.cell_clear_all(sheet, source_row, source_column)?;
Ok(())
}
@@ -157,6 +157,11 @@ impl Model {
return Err("Please use insert columns instead".to_string());
}
// first column being deleted
let column_start = column;
// last column being deleted
let column_end = column + column_count - 1;
// Move cells
let worksheet = &self.workbook.worksheet(sheet)?;
let mut all_rows: Vec<i32> = worksheet.sheet_data.keys().copied().collect();
@@ -166,11 +171,11 @@ impl Model {
for r in all_rows {
let columns: Vec<i32> = self.get_columns_for_row(sheet, r, false)?;
for col in columns {
if col >= column {
if col >= column + column_count {
if col >= column_start {
if col > column_end {
self.move_cell(sheet, r, col, r, col - column_count)?;
} else {
self.delete_cell(sheet, r, col)?;
self.cell_clear_all(sheet, r, col)?;
}
}
}
@@ -184,6 +189,64 @@ impl Model {
delta: -column_count,
}),
);
let worksheet = &mut self.workbook.worksheet_mut(sheet)?;
// deletes all the column styles
let mut new_columns = Vec::new();
for col in worksheet.cols.iter_mut() {
// range under study
let min = col.min;
let max = col.max;
// In the diagram:
// |xxxxx| range we are studying [min, max]
// |*****| range we are deleting [column_start, column_end]
// we are going to split it in three big cases:
// ----------------|xxxxxxxx|-----------------
// -----|*****|------------------------------- Case A
// -------|**********|------------------------ Case B
// -------------|**************|-------------- Case C
// ------------------|****|------------------- Case D
// ---------------------|**********|---------- Case E
// -----------------------------|*****|------- Case F
if column_start < min {
if column_end < min {
// Case A
// We displace all columns
let mut new_column = col.clone();
new_column.min = min - column_count;
new_column.max = max - column_count;
new_columns.push(new_column);
} else if column_end < max {
// Case B
// We displace the end
let mut new_column = col.clone();
new_column.min = column_start;
new_column.max = max - column_count;
new_columns.push(new_column);
} else {
// Case C
// skip this, we are deleting the whole range
}
} else if column_start <= max {
if column_end <= max {
// Case D
// We displace the end
let mut new_column = col.clone();
new_column.max = max - column_count;
new_columns.push(new_column);
} else {
// Case E
let mut new_column = col.clone();
new_column.max = column_start - 1;
new_columns.push(new_column);
}
} else {
// Case F
// No action required
new_columns.push(col.clone());
}
}
worksheet.cols = new_columns;
Ok(())
}
@@ -283,7 +346,7 @@ impl Model {
// remove all cells in row
// FIXME: We could just remove the entire row in one go
for column in columns {
self.delete_cell(sheet, r, column)?;
self.cell_clear_all(sheet, r, column)?;
}
}
}

View File

@@ -165,7 +165,8 @@ impl Model {
message: "argument must be a reference to a single cell".to_string(),
};
}
let is_formula = if let Ok(f) = self.cell_formula(left.sheet, left.row, left.column) {
let is_formula = if let Ok(f) = self.get_cell_formula(left.sheet, left.row, left.column)
{
f.is_some()
} else {
false

View File

@@ -8,7 +8,7 @@
//!
//! ```toml
//! [dependencies]
//! ironcalc_base = { git = "https://github.com/ironcalc/IronCalc", version = "0.1"}
//! ironcalc_base = { git = "https://github.com/ironcalc/IronCalc" }
//! ```
//!
//! <small> until version 0.5.0 you should use the git dependencies as stated </small>
@@ -31,23 +31,21 @@ pub mod expressions;
pub mod formatter;
pub mod language;
pub mod locale;
pub mod model;
pub mod new_empty;
pub mod number_format;
pub mod types;
pub mod worksheet;
mod functions;
mod actions;
mod cast;
mod constants;
mod styles;
mod diffs;
mod functions;
mod implicit_intersection;
mod model;
mod styles;
mod units;
mod user_model;
mod utils;
mod workbook;
@@ -56,3 +54,7 @@ mod test;
#[cfg(test)]
pub mod mock_time;
pub use model::get_milliseconds_since_epoch;
pub use model::Model;
pub use user_model::UserModel;

View File

@@ -1,12 +1,5 @@
#![deny(missing_docs)]
//! # Model
//!
//! Note that sheets are 0-indexed and rows and columns are 1-indexed.
//!
//! IronCalc is row first. A cell is referenced by (`sheet`, `row`, `column`)
//!
use serde_json::json;
use std::collections::HashMap;
@@ -47,7 +40,11 @@ use chrono_tz::Tz;
#[cfg(test)]
pub use crate::mock_time::get_milliseconds_since_epoch;
/// wasm implementation for time
/// Number of milliseconds since January 1, 1970
/// Used by time and date functions. It takes the value from the environment:
/// * The Operative System
/// * The JavaScript environment
/// * Or mocked for tests
#[cfg(not(test))]
#[cfg(not(target_arch = "wasm32"))]
pub fn get_milliseconds_since_epoch() -> i64 {
@@ -67,7 +64,7 @@ pub fn get_milliseconds_since_epoch() -> i64 {
/// A cell might be evaluated or being evaluated
#[derive(Clone)]
pub enum CellState {
pub(crate) enum CellState {
/// The cell has already been evaluated
Evaluated,
/// The cell is being evaluated
@@ -75,7 +72,7 @@ pub enum CellState {
}
/// A parsed formula for a defined name
pub enum ParsedDefinedName {
pub(crate) enum ParsedDefinedName {
/// CellReference (`=C4`)
CellReference(CellReferenceIndex),
/// A Range (`=C4:D6`)
@@ -105,19 +102,19 @@ pub struct Model {
/// A list of parsed formulas
pub parsed_formulas: Vec<Vec<Node>>,
/// A list of parsed defined names
pub parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
pub(crate) parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
/// An optimization to lookup strings faster
pub shared_strings: HashMap<String, usize>,
pub(crate) shared_strings: HashMap<String, usize>,
/// An instance of the parser
pub parser: Parser,
pub(crate) parser: Parser,
/// The list of cells with formulas that are evaluated of being evaluated
pub cells: HashMap<(u32, i32, i32), CellState>,
pub(crate) cells: HashMap<(u32, i32, i32), CellState>,
/// The locale of the model
pub locale: Locale,
pub(crate) locale: Locale,
/// Tha language used
pub language: Language,
pub(crate) language: Language,
/// The timezone used to evaluate the model
pub tz: Tz,
pub(crate) tz: Tz,
}
// FIXME: Maybe this should be the same as CellReference
@@ -659,7 +656,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// assert_eq!(model.workbook.worksheet(0)?.color, None);
@@ -726,7 +723,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// assert_eq!(model.is_empty_cell(0, 1, 1)?, true);
@@ -803,7 +800,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::cell::CellValue;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -827,7 +824,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::cell::CellValue;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -896,7 +893,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -965,7 +962,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::expressions::types::{Area, CellReferenceIndex};
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -1036,7 +1033,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1083,7 +1080,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -1138,13 +1135,13 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
/// model.set_user_input(sheet, row, column, "=SIN(B1*C3)+1".to_string());
/// model.evaluate();
/// let result = model.cell_formula(sheet, row, column)?;
/// let result = model.get_cell_formula(sheet, row, column)?;
/// assert_eq!(result, Some("=SIN(B1*C3)+1".to_string()));
/// # Ok(())
/// # }
@@ -1152,24 +1149,33 @@ impl Model {
///
/// See also:
/// * [Model::get_cell_content()]
pub fn cell_formula(
pub fn get_cell_formula(
&self,
sheet: u32,
row: i32,
column: i32,
) -> Result<Option<String>, String> {
let worksheet = self.workbook.worksheet(sheet)?;
Ok(worksheet.cell(row, column).and_then(|cell| {
cell.get_formula().map(|formula_index| {
let formula = &self.parsed_formulas[sheet as usize][formula_index as usize];
match worksheet.cell(row, column) {
Some(cell) => match cell.get_formula() {
Some(formula_index) => {
let formula = &self
.parsed_formulas
.get(sheet as usize)
.ok_or("missing sheet")?
.get(formula_index as usize)
.ok_or("missing formula")?;
let cell_ref = CellReferenceRC {
sheet: worksheet.get_name(),
row,
column,
};
format!("={}", to_string(formula, &cell_ref))
})
}))
Ok(Some(format!("={}", to_string(formula, &cell_ref))))
}
None => Ok(None),
},
None => Ok(None),
}
}
/// Updates the value of a cell with some text
@@ -1178,7 +1184,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1221,7 +1227,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1258,7 +1264,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1296,7 +1302,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
@@ -1348,7 +1354,7 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # use ironcalc_base::cell::CellValue;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
@@ -1358,7 +1364,7 @@ impl Model {
/// model.set_user_input(0, 1, 2, "=SUM(A:A)".to_string());
/// model.evaluate();
/// assert_eq!(model.get_cell_value_by_index(0, 1, 2), Ok(CellValue::Number(215.0)));
/// assert_eq!(model.formatted_cell_value(0, 1, 2), Ok("215$".to_string()));
/// assert_eq!(model.get_formatted_cell_value(0, 1, 2), Ok("215$".to_string()));
/// # Ok(())
/// # }
/// ```
@@ -1529,7 +1535,7 @@ impl Model {
/// Returns the cell value for (`sheet`, `row`, `column`)
///
/// See also:
/// * [Model::formatted_cell_value()]
/// * [Model::get_formatted_cell_value()]
pub fn get_cell_value_by_index(
&self,
sheet_index: u32,
@@ -1555,36 +1561,35 @@ impl Model {
/// # Examples
///
/// ```rust
/// # use ironcalc_base::model::Model;
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
/// model.set_user_input(sheet, row, column, "=1/3".to_string());
/// model.evaluate();
/// let result = model.formatted_cell_value(sheet, row, column)?;
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
/// assert_eq!(result, "0.333333333".to_string());
/// # Ok(())
/// # }
/// ```
pub fn formatted_cell_value(
pub fn get_formatted_cell_value(
&self,
sheet_index: u32,
row: i32,
column: i32,
) -> Result<String, String> {
match self.workbook.worksheet(sheet_index)?.cell(row, column) {
Some(cell) => {
let format = self.get_style_for_cell(sheet_index, row, column).num_fmt;
let cell = self
.workbook
.worksheet(sheet_index)?
.cell(row, column)
.cloned()
.unwrap_or_default();
let formatted_value =
cell.formatted_value(&self.workbook.shared_strings, &self.language, |value| {
format_number(value, &format, &self.locale).text
});
Ok(formatted_value)
}
None => Ok("".to_string()),
}
}
/// Returns a string with the cell content. If there is a formula returns the formula
/// If the cell is empty returns the empty string
@@ -1647,15 +1652,53 @@ impl Model {
}
}
/// Sets cell to empty. Can be used to delete value without affecting style.
pub fn set_cell_empty(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
let worksheet = self.workbook.worksheet_mut(sheet)?;
worksheet.set_cell_empty(row, column);
/// Removes the content of the cell but leaves the style.
///
/// See also:
/// * [Model::cell_clear_all()]
///
/// # Examples
///
/// ```rust
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
/// model.set_user_input(sheet, row, column, "100$".to_string());
/// model.cell_clear_contents(sheet, row, column);
/// model.set_user_input(sheet, row, column, "10".to_string());
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
/// assert_eq!(result, "10$".to_string());
/// # Ok(())
/// # }
/// ```
pub fn cell_clear_contents(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
self.workbook
.worksheet_mut(sheet)?
.cell_clear_contents(row, column);
Ok(())
}
/// Deletes a cell by removing it from worksheet data.
pub fn delete_cell(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
/// Deletes a cell by removing it from worksheet data. All content and style is removed.
///
/// See also:
/// * [Model::cell_clear_contents()]
///
/// # Examples
///
/// ```rust
/// # use ironcalc_base::Model;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut model = Model::new_empty("model", "en", "UTC")?;
/// let (sheet, row, column) = (0, 1, 1);
/// model.set_user_input(sheet, row, column, "100$".to_string());
/// model.cell_clear_all(sheet, row, column);
/// model.set_user_input(sheet, row, column, "10".to_string());
/// let result = model.get_formatted_cell_value(sheet, row, column)?;
/// assert_eq!(result, "10".to_string());
/// # Ok(())
/// # }
pub fn cell_clear_all(&mut self, sheet: u32, row: i32, column: i32) -> Result<(), String> {
let worksheet = self.workbook.worksheet_mut(sheet)?;
let sheet_data = &mut worksheet.sheet_data;
@@ -1682,9 +1725,8 @@ impl Model {
if r.r == row {
if r.custom_format {
return r.s;
} else {
break;
}
break;
}
}
let cols = &self.workbook.worksheets[sheet as usize].cols;
@@ -1718,8 +1760,22 @@ impl Model {
}
}
/// Returns data about the worksheets
pub fn get_sheets_info(&self) -> Vec<SheetInfo> {
self.workbook
.worksheets
.iter()
.map(|worksheet| SheetInfo {
name: worksheet.get_name(),
state: worksheet.state.to_string(),
color: worksheet.color.clone(),
sheet_id: worksheet.sheet_id,
})
.collect()
}
/// Returns markup representation of the given `sheet`.
pub fn sheet_markup(&self, sheet: u32) -> Result<String, String> {
pub fn get_sheet_markup(&self, sheet: u32) -> Result<String, String> {
let worksheet = self.workbook.worksheet(sheet)?;
let dimension = worksheet.dimension();
@@ -1729,9 +1785,9 @@ impl Model {
let mut row_markup: Vec<String> = Vec::new();
for column in 1..(dimension.max_column + 1) {
let mut cell_markup = match self.cell_formula(sheet, row, column)? {
let mut cell_markup = match self.get_cell_formula(sheet, row, column)? {
Some(formula) => formula,
None => self.formatted_cell_value(sheet, row, column)?,
None => self.get_formatted_cell_value(sheet, row, column)?,
};
let style = self.get_style_for_cell(sheet, row, column);
if style.font.b {
@@ -1770,7 +1826,7 @@ impl Model {
}
/// Returns the number of frozen rows in `sheet`
pub fn get_frozen_rows(&self, sheet: u32) -> Result<i32, String> {
pub fn get_frozen_rows_count(&self, sheet: u32) -> Result<i32, String> {
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
Ok(worksheet.frozen_rows)
} else {
@@ -1779,7 +1835,7 @@ impl Model {
}
/// Return the number of frozen columns in `sheet`
pub fn get_frozen_columns(&self, sheet: u32) -> Result<i32, String> {
pub fn get_frozen_columns_count(&self, sheet: u32) -> Result<i32, String> {
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
Ok(worksheet.frozen_columns)
} else {
@@ -1820,6 +1876,34 @@ impl Model {
Err("Invalid sheet".to_string())
}
}
/// Returns the width of a column
#[inline]
pub fn get_column_width(&self, sheet: u32, column: i32) -> Result<f64, String> {
self.workbook.worksheet(sheet)?.get_column_width(column)
}
/// Sets the width of a column
#[inline]
pub fn set_column_width(&mut self, sheet: u32, column: i32, width: f64) -> Result<(), String> {
self.workbook
.worksheet_mut(sheet)?
.set_column_width(column, width)
}
/// Returns the height of a row
#[inline]
pub fn get_row_height(&self, sheet: u32, row: i32) -> Result<f64, String> {
self.workbook.worksheet(sheet)?.row_height(row)
}
/// Sets the height of a row
#[inline]
pub fn set_row_height(&mut self, sheet: u32, column: i32, height: f64) -> Result<(), String> {
self.workbook
.worksheet_mut(sheet)?
.set_row_height(column, height)
}
}
#[cfg(test)]

View File

@@ -123,7 +123,7 @@ impl Model {
}
// Reparses all formulas and defined names
fn reset_parsed_structures(&mut self) {
pub(crate) fn reset_parsed_structures(&mut self) {
self.parser
.set_worksheets(self.workbook.get_worksheet_names());
self.parsed_formulas = vec![];
@@ -134,10 +134,10 @@ impl Model {
}
/// Adds a sheet with a automatically generated name
pub fn new_sheet(&mut self) {
pub fn new_sheet(&mut self) -> (String, u32) {
// First we find a name
// TODO: When/if we support i18n the name could depend on the locale
// TODO: The name should depend on the locale
let base_name = "Sheet";
let base_name_uppercase = base_name.to_uppercase();
let mut index = 1;
@@ -156,6 +156,7 @@ impl Model {
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id);
self.workbook.worksheets.push(worksheet);
self.reset_parsed_structures();
(sheet_name, self.workbook.worksheets.len() as u32 - 1)
}
/// Inserts a sheet with a particular index
@@ -223,10 +224,10 @@ impl Model {
new_name: &str,
) -> Result<(), String> {
if !is_valid_sheet_name(new_name) {
return Err(format!("Invalid name for a sheet: '{}'", new_name));
return Err(format!("Invalid name for a sheet: '{}'.", new_name));
}
if self.get_sheet_index_by_name(new_name).is_some() {
return Err(format!("Sheet already exists: '{}'", new_name));
return Err(format!("Sheet already exists: '{}'.", new_name));
}
let worksheets = &self.workbook.worksheets;
let sheet_count = worksheets.len() as u32;
@@ -270,7 +271,7 @@ impl Model {
if sheet_count == 1 {
return Err("Cannot delete only sheet".to_string());
};
if sheet_index > sheet_count {
if sheet_index >= sheet_count {
return Err("Sheet index too large".to_string());
}
self.workbook.worksheets.remove(sheet_index as usize);

View File

@@ -1,6 +1,7 @@
mod test_actions;
mod test_binary_search;
mod test_cell;
mod test_cell_clear_contents;
mod test_circular_references;
mod test_column_width;
mod test_criteria;
@@ -28,9 +29,8 @@ mod test_frozen_rows_columns;
mod test_general;
mod test_math;
mod test_metadata;
mod test_model_delete_cell;
mod test_model_cell_clear_all;
mod test_model_is_empty_cell;
mod test_model_set_cell_empty;
mod test_move_formula;
mod test_quote_prefix;
mod test_set_user_input;
@@ -53,3 +53,4 @@ mod test_frozen_rows_and_columns;
mod test_get_cell_content;
mod test_percentage;
mod test_today;
mod user_model;

View File

@@ -3,6 +3,7 @@
use crate::constants::LAST_COLUMN;
use crate::model::Model;
use crate::test::util::new_empty_model;
use crate::types::Col;
#[test]
fn test_insert_columns() {
@@ -195,6 +196,250 @@ fn test_delete_columns() {
assert_eq!(model._get_formula("A3"), *"=SUM(#REF!:K4)");
}
#[test]
fn test_delete_column_width() {
let mut model = new_empty_model();
let (sheet, column) = (0, 5);
let normal_width = model.get_column_width(sheet, column).unwrap();
// Set the width of one column to 5 times the normal width
assert!(model
.set_column_width(sheet, column, normal_width * 5.0)
.is_ok());
// delete it
assert!(model.delete_columns(sheet, column, 1).is_ok());
// all the columns around have the expected width
assert_eq!(
model.get_column_width(sheet, column - 1).unwrap(),
normal_width
);
assert_eq!(model.get_column_width(sheet, column).unwrap(), normal_width);
assert_eq!(
model.get_column_width(sheet, column + 1).unwrap(),
normal_width
);
}
#[test]
// We set the style of columns 4 to 7 and delete column 4
// We check that columns 4 to 6 have the new style
fn test_delete_first_column_width() {
let mut model = new_empty_model();
model.workbook.worksheets[0].cols = vec![Col {
min: 4,
max: 7,
width: 300.0,
custom_width: true,
style: None,
}];
let (sheet, column) = (0, 4);
assert!(model.delete_columns(sheet, column, 1).is_ok());
let cols = &model.workbook.worksheets[0].cols;
assert_eq!(cols.len(), 1);
assert_eq!(
cols[0],
Col {
min: 4,
max: 6,
width: 300.0,
custom_width: true,
style: None
}
);
}
#[test]
// Delete the last column in the range
fn test_delete_last_column_width() {
let mut model = new_empty_model();
model.workbook.worksheets[0].cols = vec![Col {
min: 4,
max: 7,
width: 300.0,
custom_width: true,
style: None,
}];
let (sheet, column) = (0, 7);
assert!(model.delete_columns(sheet, column, 1).is_ok());
let cols = &model.workbook.worksheets[0].cols;
assert_eq!(cols.len(), 1);
assert_eq!(
cols[0],
Col {
min: 4,
max: 6,
width: 300.0,
custom_width: true,
style: None
}
);
}
#[test]
// Deletes columns at the end
fn test_delete_last_few_columns_width() {
let mut model = new_empty_model();
model.workbook.worksheets[0].cols = vec![Col {
min: 4,
max: 17,
width: 300.0,
custom_width: true,
style: None,
}];
let (sheet, column) = (0, 13);
assert!(model.delete_columns(sheet, column, 10).is_ok());
let cols = &model.workbook.worksheets[0].cols;
assert_eq!(cols.len(), 1);
assert_eq!(
cols[0],
Col {
min: 4,
max: 12,
width: 300.0,
custom_width: true,
style: None
}
);
}
#[test]
fn test_delete_columns_non_overlapping_left() {
let mut model = new_empty_model();
model.workbook.worksheets[0].cols = vec![Col {
min: 10,
max: 17,
width: 300.0,
custom_width: true,
style: None,
}];
let (sheet, column) = (0, 3);
assert!(model.delete_columns(sheet, column, 4).is_ok());
let cols = &model.workbook.worksheets[0].cols;
assert_eq!(cols.len(), 1);
assert_eq!(
cols[0],
Col {
min: 6,
max: 13,
width: 300.0,
custom_width: true,
style: None
}
);
}
#[test]
fn test_delete_columns_overlapping_left() {
let mut model = new_empty_model();
model.workbook.worksheets[0].cols = vec![Col {
min: 10,
max: 20,
width: 300.0,
custom_width: true,
style: None,
}];
let (sheet, column) = (0, 8);
assert!(model.delete_columns(sheet, column, 4).is_ok());
let cols = &model.workbook.worksheets[0].cols;
assert_eq!(cols.len(), 1);
assert_eq!(
cols[0],
Col {
min: 8,
max: 16,
width: 300.0,
custom_width: true,
style: None
}
);
}
#[test]
fn test_delete_columns_non_overlapping_right() {
let mut model = new_empty_model();
model.workbook.worksheets[0].cols = vec![Col {
min: 10,
max: 17,
width: 300.0,
custom_width: true,
style: None,
}];
let (sheet, column) = (0, 23);
assert!(model.delete_columns(sheet, column, 4).is_ok());
let cols = &model.workbook.worksheets[0].cols;
assert_eq!(cols.len(), 1);
assert_eq!(
cols[0],
Col {
min: 10,
max: 17,
width: 300.0,
custom_width: true,
style: None
}
);
}
#[test]
// deletes some columns in the middle of the range
fn test_delete_middle_column_width() {
let mut model = new_empty_model();
// styled columns [4, 17]
model.workbook.worksheets[0].cols = vec![Col {
min: 4,
max: 17,
width: 300.0,
custom_width: true,
style: None,
}];
// deletes columns 10, 11, 12
let (sheet, column) = (0, 10);
assert!(model.delete_columns(sheet, column, 3).is_ok());
let cols = &model.workbook.worksheets[0].cols;
assert_eq!(cols.len(), 1);
assert_eq!(
cols[0],
Col {
min: 4,
max: 14,
width: 300.0,
custom_width: true,
style: None
}
);
}
#[test]
// the range is inside the deleted columns
fn delete_range_in_columns() {
let mut model = new_empty_model();
// styled columns [6, 10]
model.workbook.worksheets[0].cols = vec![Col {
min: 6,
max: 10,
width: 300.0,
custom_width: true,
style: None,
}];
// deletes columns [4, 17]
let (sheet, column) = (0, 4);
assert!(model.delete_columns(sheet, column, 8).is_ok());
let cols = &model.workbook.worksheets[0].cols;
assert_eq!(cols.len(), 0);
}
#[test]
fn test_delete_columns_error() {
let mut model = new_empty_model();
let (sheet, column) = (0, 5);
assert!(model.delete_columns(sheet, column, -1).is_err());
assert!(model.delete_columns(sheet, column, 0).is_err());
assert!(model.delete_columns(sheet, column, 1).is_ok());
}
#[test]
fn test_delete_rows() {
let mut model = new_empty_model();

View File

@@ -2,25 +2,25 @@
use crate::test::util::new_empty_model;
#[test]
fn test_set_cell_empty_non_existing_sheet() {
fn test_cell_clear_contents_non_existing_sheet() {
let mut model = new_empty_model();
assert_eq!(
model.set_cell_empty(13, 1, 1),
model.cell_clear_contents(13, 1, 1),
Err("Invalid sheet index".to_string())
);
}
#[test]
fn test_set_cell_empty_unset_cell() {
fn test_cell_clear_contents_unset_cell() {
let mut model = new_empty_model();
model.set_cell_empty(0, 1, 1).unwrap();
model.cell_clear_contents(0, 1, 1).unwrap();
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(true));
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");
}
#[test]
fn test_set_cell_empty_with_value() {
fn test_cell_clear_contents_with_value() {
let mut model = new_empty_model();
model._set("A1", "hello");
model.evaluate();
@@ -28,7 +28,7 @@ fn test_set_cell_empty_with_value() {
assert_eq!(model._get_text_at(0, 1, 1), "hello");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
model.set_cell_empty(0, 1, 1).unwrap();
model.cell_clear_contents(0, 1, 1).unwrap();
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");
@@ -36,7 +36,7 @@ fn test_set_cell_empty_with_value() {
}
#[test]
fn test_set_cell_empty_referenced_elsewhere() {
fn test_cell_clear_contents_referenced_elsewhere() {
let mut model = new_empty_model();
model._set("A1", "35");
model._set("A2", "=2*A1");
@@ -47,7 +47,7 @@ fn test_set_cell_empty_referenced_elsewhere() {
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
model.set_cell_empty(0, 1, 1).unwrap();
model.cell_clear_contents(0, 1, 1).unwrap();
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");

View File

@@ -23,9 +23,9 @@ fn test_column_width() {
.unwrap();
assert_eq!(model.workbook.worksheets[0].cols.len(), 3);
let worksheet = model.workbook.worksheet(0).unwrap();
assert!((worksheet.column_width(1).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.column_width(2).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.column_width(3).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.get_column_width(1).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.get_column_width(2).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.get_column_width(3).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert_eq!(model.get_cell_style_index(0, 23, 2), 6);
}
@@ -48,9 +48,11 @@ fn test_column_width_lower_edge() {
.unwrap();
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
let worksheet = model.workbook.worksheet(0).unwrap();
assert!((worksheet.column_width(4).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.column_width(5).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.column_width(6).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON);
assert!((worksheet.get_column_width(4).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.get_column_width(5).unwrap() - 30.0).abs() < f64::EPSILON);
assert!(
(worksheet.get_column_width(6).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
);
assert_eq!(model.get_cell_style_index(0, 23, 5), 1);
}
@@ -74,9 +76,9 @@ fn test_column_width_higher_edge() {
assert_eq!(model.workbook.worksheets[0].cols.len(), 2);
let worksheet = model.workbook.worksheet(0).unwrap();
assert!(
(worksheet.column_width(15).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
(worksheet.get_column_width(15).unwrap() - 10.0 * COLUMN_WIDTH_FACTOR).abs() < f64::EPSILON
);
assert!((worksheet.column_width(16).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.column_width(17).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert!((worksheet.get_column_width(16).unwrap() - 30.0).abs() < f64::EPSILON);
assert!((worksheet.get_column_width(17).unwrap() - DEFAULT_COLUMN_WIDTH).abs() < f64::EPSILON);
assert_eq!(model.get_cell_style_index(0, 23, 16), 1);
}

View File

@@ -8,34 +8,37 @@ use crate::{
#[test]
fn test_empty_model() {
let mut model = new_empty_model();
assert_eq!(model.get_frozen_rows(0), Ok(0));
assert_eq!(model.get_frozen_columns(0), Ok(0));
assert_eq!(model.get_frozen_rows_count(0), Ok(0));
assert_eq!(model.get_frozen_columns_count(0), Ok(0));
let e = model.set_frozen_rows(0, 3);
assert!(e.is_ok());
assert_eq!(model.get_frozen_rows(0), Ok(3));
assert_eq!(model.get_frozen_columns(0), Ok(0));
assert_eq!(model.get_frozen_rows_count(0), Ok(3));
assert_eq!(model.get_frozen_columns_count(0), Ok(0));
let e = model.set_frozen_columns(0, 53);
assert!(e.is_ok());
assert_eq!(model.get_frozen_rows(0), Ok(3));
assert_eq!(model.get_frozen_columns(0), Ok(53));
assert_eq!(model.get_frozen_rows_count(0), Ok(3));
assert_eq!(model.get_frozen_columns_count(0), Ok(53));
// Set them back to zero
let e = model.set_frozen_rows(0, 0);
assert!(e.is_ok());
let e = model.set_frozen_columns(0, 0);
assert!(e.is_ok());
assert_eq!(model.get_frozen_rows(0), Ok(0));
assert_eq!(model.get_frozen_columns(0), Ok(0));
assert_eq!(model.get_frozen_rows_count(0), Ok(0));
assert_eq!(model.get_frozen_columns_count(0), Ok(0));
}
#[test]
fn test_invalid_sheet() {
let mut model = new_empty_model();
assert_eq!(model.get_frozen_rows(1), Err("Invalid sheet".to_string()));
assert_eq!(
model.get_frozen_columns(3),
model.get_frozen_rows_count(1),
Err("Invalid sheet".to_string())
);
assert_eq!(
model.get_frozen_columns_count(3),
Err("Invalid sheet".to_string())
);

View File

@@ -411,11 +411,11 @@ fn test_get_formatted_cell_value() {
model.evaluate();
assert_eq!(model.formatted_cell_value(0, 1, 1).unwrap(), "foobar");
assert_eq!(model.formatted_cell_value(0, 2, 1).unwrap(), "TRUE");
assert_eq!(model.formatted_cell_value(0, 3, 1).unwrap(), "");
assert_eq!(model.formatted_cell_value(0, 4, 1).unwrap(), "123.456");
assert_eq!(model.formatted_cell_value(0, 5, 1).unwrap(), "$123.46");
assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "foobar");
assert_eq!(model.get_formatted_cell_value(0, 2, 1).unwrap(), "TRUE");
assert_eq!(model.get_formatted_cell_value(0, 3, 1).unwrap(), "");
assert_eq!(model.get_formatted_cell_value(0, 4, 1).unwrap(), "123.456");
assert_eq!(model.get_formatted_cell_value(0, 5, 1).unwrap(), "$123.46");
}
#[test]
@@ -426,20 +426,20 @@ fn test_cell_formula() {
model.evaluate();
assert_eq!(
model.cell_formula(0, 1, 1), // A1
model.get_cell_formula(0, 1, 1), // A1
Ok(Some("=1+2+3".to_string())),
);
assert_eq!(
model.cell_formula(0, 2, 1), // A2
model.get_cell_formula(0, 2, 1), // A2
Ok(None),
);
assert_eq!(
model.cell_formula(0, 3, 1), // A3 - empty cell
model.get_cell_formula(0, 3, 1), // A3 - empty cell
Ok(None),
);
assert_eq!(
model.cell_formula(42, 1, 1),
model.get_cell_formula(42, 1, 1),
Err("Invalid sheet index".to_string()),
);
}
@@ -453,16 +453,16 @@ fn test_xlfn() {
model.evaluate();
// Only modern formulas strip the '_xlfn.'
assert_eq!(
model.cell_formula(0, 1, 1).unwrap(),
model.get_cell_formula(0, 1, 1).unwrap(),
Some("=_xlfn.SIN(1)".to_string())
);
// unknown formulas keep the '_xlfn.' prefix
assert_eq!(
model.cell_formula(0, 2, 1).unwrap(),
model.get_cell_formula(0, 2, 1).unwrap(),
Some("=_xlfn.SINY(1)".to_string())
);
assert_eq!(
model.cell_formula(0, 3, 1).unwrap(),
model.get_cell_formula(0, 3, 1).unwrap(),
Some("=CONCAT(3,4)".to_string())
);
}
@@ -474,11 +474,11 @@ fn test_letter_case() {
model._set("A2", "=sIn(2)");
model.evaluate();
assert_eq!(
model.cell_formula(0, 1, 1).unwrap(),
model.get_cell_formula(0, 1, 1).unwrap(),
Some("=SIN(1)".to_string())
);
assert_eq!(
model.cell_formula(0, 2, 1).unwrap(),
model.get_cell_formula(0, 2, 1).unwrap(),
Some("=SIN(2)".to_string())
);
}

View File

@@ -2,22 +2,22 @@
use crate::test::util::new_empty_model;
#[test]
fn test_delete_cell_non_existing_sheet() {
fn test_cell_clear_all_non_existing_sheet() {
let mut model = new_empty_model();
assert_eq!(
model.delete_cell(13, 1, 1),
model.cell_clear_all(13, 1, 1),
Err("Invalid sheet index".to_string())
);
}
#[test]
fn test_delete_cell_unset_cell() {
fn test_cell_clear_all_unset_cell() {
let mut model = new_empty_model();
assert!(model.delete_cell(0, 1, 1).is_ok());
assert!(model.cell_clear_all(0, 1, 1).is_ok());
}
#[test]
fn test_delete_cell_with_value() {
fn test_cell_clear_all_with_value() {
let mut model = new_empty_model();
model._set("A1", "hello");
model.evaluate();
@@ -25,7 +25,7 @@ fn test_delete_cell_with_value() {
assert_eq!(model._get_text_at(0, 1, 1), "hello");
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
model.delete_cell(0, 1, 1).unwrap();
model.cell_clear_all(0, 1, 1).unwrap();
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");
@@ -33,7 +33,7 @@ fn test_delete_cell_with_value() {
}
#[test]
fn test_delete_cell_referenced_elsewhere() {
fn test_cell_clear_all_referenced_elsewhere() {
let mut model = new_empty_model();
model._set("A1", "35");
model._set("A2", "=2*A1");
@@ -44,7 +44,7 @@ fn test_delete_cell_referenced_elsewhere() {
assert_eq!(model.is_empty_cell(0, 1, 1), Ok(false));
assert_eq!(model.is_empty_cell(0, 2, 1), Ok(false));
model.delete_cell(0, 1, 1).unwrap();
model.cell_clear_all(0, 1, 1).unwrap();
model.evaluate();
assert_eq!(model._get_text_at(0, 1, 1), "");

View File

@@ -16,7 +16,7 @@ fn test_is_empty_cell() {
assert!(model.is_empty_cell(0, 3, 1).unwrap());
model.set_user_input(0, 3, 1, "Hello World".to_string());
assert!(!model.is_empty_cell(0, 3, 1).unwrap());
model.set_cell_empty(0, 3, 1).unwrap();
model.cell_clear_contents(0, 3, 1).unwrap();
assert!(model.is_empty_cell(0, 3, 1).unwrap());
}

View File

@@ -21,7 +21,7 @@ fn test_sheet_markup() {
model.set_cell_style(0, 4, 1, &style).unwrap();
assert_eq!(
model.sheet_markup(0),
model.get_sheet_markup(0),
Ok("**Item**|**Cost**\nRent|$600\nElectricity|$200\n**Total**|=SUM(B2:B3)".to_string()),
)
}

View File

@@ -236,3 +236,11 @@ fn test_delete_sheet_by_index() {
assert_eq!(model.workbook.get_worksheet_names(), ["Sheet2"]);
assert_eq!(model._get_text("Sheet2!A1"), "#REF!");
}
#[test]
fn delete_sheet_error() {
let mut model = new_empty_model();
model.new_sheet();
assert!(model.delete_sheet(2).is_err());
assert!(model.delete_sheet(1).is_ok());
}

View File

@@ -5,7 +5,7 @@ use crate::{test::util::new_empty_model, types::SheetInfo};
#[test]
fn workbook_worksheets_info() {
let model = new_empty_model();
let sheets_info = model.workbook.get_worksheets_info();
let sheets_info = model.get_sheets_info();
assert_eq!(
sheets_info[0],
SheetInfo {

View File

@@ -39,7 +39,7 @@ fn test_worksheet_dimension_single_cell() {
fn test_worksheet_dimension_single_cell_set_empty() {
let mut model = new_empty_model();
model._set("W11", "1");
model.set_cell_empty(0, 11, 23).unwrap();
model.cell_clear_contents(0, 11, 23).unwrap();
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
@@ -55,7 +55,7 @@ fn test_worksheet_dimension_single_cell_set_empty() {
fn test_worksheet_dimension_single_cell_deleted() {
let mut model = new_empty_model();
model._set("W11", "1");
model.delete_cell(0, 11, 23).unwrap();
model.cell_clear_all(0, 11, 23).unwrap();
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {
@@ -75,7 +75,7 @@ fn test_worksheet_dimension_multiple_cells() {
model._set("AA17", "1");
model._set("G17", "1");
model._set("B19", "1");
model.delete_cell(0, 11, 23).unwrap();
model.cell_clear_all(0, 11, 23).unwrap();
assert_eq!(
model.workbook.worksheet(0).unwrap().dimension(),
WorksheetDimension {

View File

@@ -0,0 +1,9 @@
mod test_add_delete_sheets;
mod test_clear_cells;
mod test_diff_queue;
mod test_evaluation;
mod test_general;
mod test_rename_sheet;
mod test_row_column;
mod test_styles;
mod test_undo_redo;

View File

@@ -0,0 +1,58 @@
#![allow(clippy::unwrap_used)]
use crate::{constants::DEFAULT_COLUMN_WIDTH, UserModel};
#[test]
fn add_undo_redo() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model.new_sheet();
model.set_user_input(1, 1, 1, "=1 + 1").unwrap();
model.set_user_input(1, 1, 2, "=A1*3").unwrap();
model
.set_column_width(1, 5, 5.0 * DEFAULT_COLUMN_WIDTH)
.unwrap();
model.new_sheet();
model.set_user_input(2, 1, 1, "=Sheet2!B1").unwrap();
model.undo().unwrap();
model.undo().unwrap();
assert!(model.get_formatted_cell_value(2, 1, 1).is_err());
model.redo().unwrap();
model.redo().unwrap();
assert_eq!(model.get_formatted_cell_value(2, 1, 1), Ok("6".to_string()));
model.delete_sheet(1).unwrap();
assert!(!model.can_undo());
assert!(!model.can_redo());
}
#[test]
fn new_sheet_propagates() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model.new_sheet();
let send_queue = model.flush_send_queue();
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
model2.apply_external_diffs(&send_queue).unwrap();
let sheets_info = model2.get_sheets_info();
assert_eq!(sheets_info.len(), 2);
}
#[test]
fn delete_sheet_propagates() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model.new_sheet();
model.delete_sheet(0).unwrap();
let send_queue = model.flush_send_queue();
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
model2.apply_external_diffs(&send_queue).unwrap();
let sheets_info = model2.get_sheets_info();
assert_eq!(sheets_info.len(), 1);
}

View File

@@ -0,0 +1,91 @@
#![allow(clippy::unwrap_used)]
use crate::{expressions::types::Area, UserModel};
#[test]
fn basic() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model.set_user_input(0, 1, 1, "100$").unwrap();
model
.range_clear_contents(&Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
})
.unwrap();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
model.undo().unwrap();
assert_eq!(
model.get_formatted_cell_value(0, 1, 1),
Ok("100$".to_string())
);
model.redo().unwrap();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
model.set_user_input(0, 1, 1, "300").unwrap();
// clear contents keeps the formatting
assert_eq!(
model.get_formatted_cell_value(0, 1, 1),
Ok("300$".to_string())
);
model
.range_clear_all(&Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
})
.unwrap();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
model.undo().unwrap();
assert_eq!(
model.get_formatted_cell_value(0, 1, 1),
Ok("300$".to_string())
);
model.redo().unwrap();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
model.set_user_input(0, 1, 1, "400").unwrap();
// clear contents keeps the formatting
assert_eq!(
model.get_formatted_cell_value(0, 1, 1),
Ok("400".to_string())
);
}
#[test]
fn clear_empty_cell() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model
.range_clear_contents(&Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
})
.unwrap();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
model.undo().unwrap();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
}
#[test]
fn clear_all_empty_cell() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model
.range_clear_all(&Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
})
.unwrap();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
model.undo().unwrap();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
}

View File

@@ -0,0 +1,159 @@
use crate::{
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT},
test::util::new_empty_model,
UserModel,
};
#[test]
fn send_queue() {
let mut model1 = UserModel::from_model(new_empty_model());
let width = model1.get_column_width(0, 3).unwrap() * 3.0;
model1.set_column_width(0, 3, width).unwrap();
model1.set_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
let send_queue = model1.flush_send_queue();
let mut model2 = UserModel::from_model(new_empty_model());
model2.apply_external_diffs(&send_queue).unwrap();
assert_eq!(model2.get_column_width(0, 3), Ok(width));
assert_eq!(
model2.get_formatted_cell_value(0, 1, 2),
Ok("Hello IronCalc!".to_string())
);
}
#[test]
fn apply_external_diffs_wrong_str() {
let mut model1 = UserModel::from_model(new_empty_model());
assert!(model1.apply_external_diffs("invalid").is_err());
}
#[test]
fn queue_undo_redo() {
let mut model1 = UserModel::from_model(new_empty_model());
let width = model1.get_column_width(0, 3).unwrap() * 3.0;
model1.set_column_width(0, 3, width).unwrap();
model1.set_user_input(0, 1, 2, "Hello IronCalc!").unwrap();
assert!(model1.undo().is_ok());
assert!(model1.redo().is_ok());
let send_queue = model1.flush_send_queue();
let mut model2 = UserModel::from_model(new_empty_model());
model2.apply_external_diffs(&send_queue).unwrap();
assert_eq!(model2.get_column_width(0, 3), Ok(width));
assert_eq!(
model2.get_formatted_cell_value(0, 1, 2),
Ok("Hello IronCalc!".to_string())
);
}
#[test]
fn queue_undo_redo_multiple() {
let mut model1 = UserModel::from_model(new_empty_model());
// do a bunch of things
model1.set_frozen_columns_count(0, 5).unwrap();
model1.set_frozen_rows_count(0, 6).unwrap();
model1.set_column_width(0, 7, 300.0).unwrap();
model1.set_row_height(0, 23, 123.0).unwrap();
model1.set_user_input(0, 55, 55, "=42+8").unwrap();
for row in 1..5 {
model1.set_user_input(0, row, 17, "=ROW()").unwrap();
}
model1.insert_row(0, 3).unwrap();
model1.insert_row(0, 3).unwrap();
// undo al of them
while model1.can_undo() {
model1.undo().unwrap();
}
// check it is an empty model
assert_eq!(model1.get_frozen_columns_count(0), Ok(0));
assert_eq!(model1.get_frozen_rows_count(0), Ok(0));
assert_eq!(model1.get_column_width(0, 7), Ok(DEFAULT_COLUMN_WIDTH));
assert_eq!(
model1.get_formatted_cell_value(0, 55, 55),
Ok("".to_string())
);
assert_eq!(model1.get_row_height(0, 23), Ok(DEFAULT_ROW_HEIGHT));
assert_eq!(
model1.get_formatted_cell_value(0, 57, 55),
Ok("".to_string())
);
assert_eq!(model1.get_row_height(0, 25), Ok(DEFAULT_ROW_HEIGHT));
// redo all of them
while model1.can_redo() {
model1.redo().unwrap();
}
// now send all this to a new model
let send_queue = model1.flush_send_queue();
let mut model2 = UserModel::from_model(new_empty_model());
model2.apply_external_diffs(&send_queue).unwrap();
// Check everything is as expected
assert_eq!(model2.get_frozen_columns_count(0), Ok(5));
assert_eq!(model2.get_frozen_rows_count(0), Ok(6));
assert_eq!(model2.get_column_width(0, 7), Ok(300.0));
// I inserted two rows
assert_eq!(
model2.get_formatted_cell_value(0, 57, 55),
Ok("50".to_string())
);
assert_eq!(model2.get_row_height(0, 25), Ok(123.0));
assert_eq!(
model2.get_formatted_cell_value(0, 1, 17),
Ok("1".to_string())
);
assert_eq!(
model2.get_formatted_cell_value(0, 2, 17),
Ok("2".to_string())
);
assert_eq!(
model2.get_formatted_cell_value(0, 3, 17),
Ok("".to_string())
);
assert_eq!(
model2.get_formatted_cell_value(0, 4, 17),
Ok("".to_string())
);
assert_eq!(
model2.get_formatted_cell_value(0, 5, 17),
Ok("5".to_string())
);
assert_eq!(
model2.get_formatted_cell_value(0, 6, 17),
Ok("6".to_string())
);
}
#[test]
fn new_sheet() {
let mut model1 = UserModel::from_model(new_empty_model());
model1.new_sheet();
model1.set_user_input(0, 1, 1, "42").unwrap();
model1.set_user_input(1, 1, 1, "=Sheet1!A1*2").unwrap();
let send_queue = model1.flush_send_queue();
let mut model2 = UserModel::from_model(new_empty_model());
model2.apply_external_diffs(&send_queue).unwrap();
assert_eq!(
model2.get_formatted_cell_value(1, 1, 1),
Ok("84".to_string())
);
}
#[test]
fn wrong_diffs_handled() {
let mut model = UserModel::from_model(new_empty_model());
assert!(model.apply_external_diffs("Hello world").is_err());
}

View File

@@ -0,0 +1,31 @@
#![allow(clippy::unwrap_used)]
use crate::UserModel;
#[test]
fn model_evaluates_automatically() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model.set_user_input(0, 1, 1, "=1 + 1").unwrap();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("2".to_string()));
assert_eq!(model.get_cell_content(0, 1, 1), Ok("=1+1".to_string()));
}
#[test]
fn pause_resume_evaluation() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model.pause_evaluation();
model.set_user_input(0, 1, 1, "=1+1").unwrap();
assert_eq!(
model.get_formatted_cell_value(0, 1, 1),
Ok("#ERROR!".to_string())
);
model.evaluate();
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("2".to_string()));
assert_eq!(model.get_cell_content(0, 1, 1), Ok("=1+1".to_string()));
model.resume_evaluation();
model.set_user_input(0, 2, 1, "=1+4").unwrap();
assert_eq!(model.get_formatted_cell_value(0, 2, 1), Ok("5".to_string()));
}

View File

@@ -0,0 +1,103 @@
#![allow(clippy::unwrap_used)]
use crate::constants::{LAST_COLUMN, LAST_ROW};
use crate::test::util::new_empty_model;
use crate::UserModel;
#[test]
fn set_user_input_errors() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
// Wrong sheet
assert!(model.set_user_input(1, 1, 1, "1").is_err());
// Wrong row
assert!(model.set_user_input(0, 0, 1, "1").is_err());
// Wrong column
assert!(model.set_user_input(0, 1, 0, "1").is_err());
// row too large
assert!(model.set_user_input(0, LAST_ROW, 1, "1").is_ok());
assert!(model.set_user_input(0, LAST_ROW + 1, 1, "1").is_err());
// column too large
assert!(model.set_user_input(0, 1, LAST_COLUMN, "1").is_ok());
assert!(model.set_user_input(0, 1, LAST_COLUMN + 1, "1").is_err());
}
#[test]
fn insert_remove_rows() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
let height = model.get_row_height(0, 5).unwrap();
// Insert some data in row 5 (and change the style)
assert!(model.set_user_input(0, 5, 1, "100$").is_ok());
// Change the height of the column
assert!(model.set_row_height(0, 5, 3.0 * height).is_ok());
// remove the row
assert!(model.delete_row(0, 5).is_ok());
// Row 5 has now the normal height
assert_eq!(model.get_row_height(0, 5), Ok(height));
// There is no value in A5
assert_eq!(model.get_formatted_cell_value(0, 5, 1), Ok("".to_string()));
// Setting a value will not format it
assert!(model.set_user_input(0, 5, 1, "125").is_ok());
assert_eq!(
model.get_formatted_cell_value(0, 5, 1),
Ok("125".to_string())
);
// undo twice
assert!(model.undo().is_ok());
assert!(model.undo().is_ok());
assert_eq!(model.get_row_height(0, 5), Ok(3.0 * height));
assert_eq!(
model.get_formatted_cell_value(0, 5, 1),
Ok("100$".to_string())
);
}
#[test]
fn insert_remove_columns() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
// column E
let column_width = model.get_column_width(0, 5).unwrap();
println!("{column_width}");
// Insert some data in row 5 (and change the style) in E1
assert!(model.set_user_input(0, 1, 5, "100$").is_ok());
// Change the width of the column
assert!(model.set_column_width(0, 5, 3.0 * column_width).is_ok());
assert_eq!(model.get_column_width(0, 5).unwrap(), 3.0 * column_width);
// remove the column
assert!(model.delete_column(0, 5).is_ok());
// Column 5 has now the normal width
assert_eq!(model.get_column_width(0, 5), Ok(column_width));
// There is no value in E5
assert_eq!(model.get_formatted_cell_value(0, 1, 5), Ok("".to_string()));
// Setting a value will not format it
assert!(model.set_user_input(0, 1, 5, "125").is_ok());
assert_eq!(
model.get_formatted_cell_value(0, 1, 5),
Ok("125".to_string())
);
// undo twice (set_user_input and delete_column)
assert!(model.undo().is_ok());
assert!(model.undo().is_ok());
assert_eq!(model.get_column_width(0, 5), Ok(3.0 * column_width));
assert_eq!(
model.get_formatted_cell_value(0, 1, 5),
Ok("100$".to_string())
);
}
#[test]
fn delete_remove_cell() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let (sheet, row, column) = (0, 1, 1);
model.set_user_input(sheet, row, column, "100$").unwrap();
}

View File

@@ -0,0 +1,39 @@
#![allow(clippy::unwrap_used)]
use crate::UserModel;
#[test]
fn basic_rename() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model.rename_sheet(0, "NewSheet").unwrap();
assert_eq!(model.get_sheets_info()[0].name, "NewSheet");
}
#[test]
fn undo_redo() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
model.rename_sheet(0, "NewSheet").unwrap();
model.undo().unwrap();
assert_eq!(model.get_sheets_info()[0].name, "Sheet1");
model.redo().unwrap();
assert_eq!(model.get_sheets_info()[0].name, "NewSheet");
let send_queue = model.flush_send_queue();
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
model2.apply_external_diffs(&send_queue).unwrap();
assert_eq!(model.get_sheets_info()[0].name, "NewSheet");
}
#[test]
fn errors() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
assert_eq!(
model.rename_sheet(0, ""),
Err("Invalid name for a sheet: ''.".to_string())
);
assert_eq!(
model.rename_sheet(1, "Hello"),
Err("Invalid sheet index".to_string())
);
}

View File

@@ -0,0 +1,156 @@
#![allow(clippy::unwrap_used)]
use crate::{
constants::{DEFAULT_COLUMN_WIDTH, DEFAULT_ROW_HEIGHT, LAST_COLUMN},
test::util::new_empty_model,
UserModel,
};
#[test]
fn simple_insert_row() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
let (sheet, column) = (0, 5);
for row in 1..5 {
assert!(model.set_user_input(sheet, row, column, "123").is_ok());
}
assert!(model.insert_row(sheet, 3).is_ok());
assert_eq!(
model.get_formatted_cell_value(sheet, 3, column).unwrap(),
""
);
assert!(model.undo().is_ok());
assert_eq!(
model.get_formatted_cell_value(sheet, 3, column).unwrap(),
"123"
);
assert!(model.redo().is_ok());
assert_eq!(
model.get_formatted_cell_value(sheet, 3, column).unwrap(),
""
);
}
#[test]
fn simple_insert_column() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
let (sheet, row) = (0, 5);
for column in 1..5 {
assert!(model.set_user_input(sheet, row, column, "123").is_ok());
}
assert!(model.insert_column(sheet, 3).is_ok());
assert_eq!(model.get_formatted_cell_value(sheet, row, 3).unwrap(), "");
assert!(model.undo().is_ok());
assert_eq!(
model.get_formatted_cell_value(sheet, row, 3).unwrap(),
"123"
);
assert!(model.redo().is_ok());
assert_eq!(model.get_formatted_cell_value(sheet, row, 3).unwrap(), "");
}
#[test]
fn simple_delete_column() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
model.set_user_input(0, 1, 5, "3").unwrap();
model.set_user_input(0, 2, 5, "=E1*2").unwrap();
model
.set_column_width(0, 5, DEFAULT_COLUMN_WIDTH * 3.0)
.unwrap();
model.delete_column(0, 5).unwrap();
assert_eq!(model.get_formatted_cell_value(0, 2, 5), Ok("".to_string()));
assert_eq!(model.get_column_width(0, 5), Ok(DEFAULT_COLUMN_WIDTH));
model.undo().unwrap();
assert_eq!(model.get_formatted_cell_value(0, 2, 5), Ok("6".to_string()));
assert_eq!(model.get_column_width(0, 5), Ok(DEFAULT_COLUMN_WIDTH * 3.0));
let send_queue = model.flush_send_queue();
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
model2.apply_external_diffs(&send_queue).unwrap();
assert_eq!(
model2.get_formatted_cell_value(0, 2, 5),
Ok("6".to_string())
);
assert_eq!(
model2.get_column_width(0, 5),
Ok(DEFAULT_COLUMN_WIDTH * 3.0)
);
}
#[test]
fn delete_column_errors() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
assert_eq!(
model.delete_column(1, 1),
Err("Invalid sheet index".to_string())
);
assert_eq!(
model.delete_column(0, 0),
Err("Column number '0' is not valid.".to_string())
);
assert_eq!(
model.delete_column(0, LAST_COLUMN + 1),
Err("Column number '16385' is not valid.".to_string())
);
assert_eq!(model.delete_column(0, LAST_COLUMN), Ok(()));
}
#[test]
fn simple_delete_row() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
model.set_user_input(0, 15, 4, "3").unwrap();
model.set_user_input(0, 15, 6, "=D15*2").unwrap();
model
.set_row_height(0, 15, DEFAULT_ROW_HEIGHT * 3.0)
.unwrap();
model.delete_row(0, 15).unwrap();
assert_eq!(model.get_formatted_cell_value(0, 15, 6), Ok("".to_string()));
assert_eq!(model.get_row_height(0, 15), Ok(DEFAULT_ROW_HEIGHT));
model.undo().unwrap();
assert_eq!(
model.get_formatted_cell_value(0, 15, 6),
Ok("6".to_string())
);
assert_eq!(model.get_row_height(0, 15), Ok(DEFAULT_ROW_HEIGHT * 3.0));
let send_queue = model.flush_send_queue();
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
model2.apply_external_diffs(&send_queue).unwrap();
assert_eq!(
model2.get_formatted_cell_value(0, 15, 6),
Ok("6".to_string())
);
assert_eq!(model2.get_row_height(0, 15), Ok(DEFAULT_ROW_HEIGHT * 3.0));
}
#[test]
fn simple_delete_row_no_style() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
model.set_user_input(0, 15, 4, "3").unwrap();
model.set_user_input(0, 15, 6, "=D15*2").unwrap();
model.delete_row(0, 15).unwrap();
assert_eq!(model.get_formatted_cell_value(0, 15, 6), Ok("".to_string()));
}

View File

@@ -0,0 +1,711 @@
#![allow(clippy::unwrap_used)]
use crate::{
expressions::types::Area,
types::{Alignment, BorderItem, BorderStyle, HorizontalAlignment, VerticalAlignment},
UserModel,
};
#[test]
fn basic_fonts() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(!style.font.i);
assert!(!style.font.b);
assert!(!style.font.u);
assert!(!style.font.strike);
assert_eq!(style.font.color, Some("#000000".to_owned()));
// bold
model.update_range_style(&range, "font.b", "true").unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(style.font.b);
// italics
model.update_range_style(&range, "font.i", "true").unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(style.font.i);
// underline
model.update_range_style(&range, "font.u", "true").unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(style.font.u);
// strike
model
.update_range_style(&range, "font.strike", "true")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(style.font.strike);
// color
model
.update_range_style(&range, "font.color", "#F1F1F1")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.font.color, Some("#F1F1F1".to_owned()));
while model.can_undo() {
model.undo().unwrap();
}
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(!style.font.i);
assert!(!style.font.b);
assert!(!style.font.u);
assert!(!style.font.strike);
assert_eq!(style.font.color, Some("#000000".to_owned()));
while model.can_redo() {
model.redo().unwrap();
}
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(style.font.i);
assert!(style.font.b);
assert!(style.font.u);
assert!(style.font.strike);
assert_eq!(style.font.color, Some("#F1F1F1".to_owned()));
let send_queue = model.flush_send_queue();
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
model2.apply_external_diffs(&send_queue).unwrap();
let style = model2.get_cell_style(0, 1, 1).unwrap();
assert!(style.font.i);
assert!(style.font.b);
assert!(style.font.u);
assert!(style.font.strike);
assert_eq!(style.font.color, Some("#F1F1F1".to_owned()));
}
#[test]
fn font_errors() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
assert_eq!(
model.update_range_style(&range, "font.b", "True"),
Err("Invalid value for boolean: 'True'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "font.i", "FALSE"),
Err("Invalid value for boolean: 'FALSE'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "font.bold", "true"),
Err("Invalid style path: 'font.bold'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "font.strike", ""),
Err("Invalid value for boolean: ''.".to_string())
);
// There is no cast for booleans
assert_eq!(
model.update_range_style(&range, "font.b", "1"),
Err("Invalid value for boolean: '1'.".to_string())
);
// colors don't work by name
assert_eq!(
model.update_range_style(&range, "font.color", "blue"),
Err("Invalid color: 'blue'.".to_string())
);
// No short form
assert_eq!(
model.update_range_style(&range, "font.color", "#FFF"),
Err("Invalid color: '#FFF'.".to_string())
);
}
#[test]
fn basic_fill() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.fill.bg_color, None);
// bg_color
model
.update_range_style(&range, "fill.bg_color", "#F2F2F2")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.fill.bg_color, Some("#F2F2F2".to_owned()));
let send_queue = model.flush_send_queue();
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
model2.apply_external_diffs(&send_queue).unwrap();
let style = model2.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.fill.bg_color, Some("#F2F2F2".to_owned()));
}
#[test]
fn fill_errors() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
assert!(model
.update_range_style(&range, "fill.bg_color", "#FFF")
.is_err());
}
#[test]
fn basic_format() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.num_fmt, "general");
model
.update_range_style(&range, "num_fmt", "$#,##0.0000")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.num_fmt, "$#,##0.0000");
model.undo().unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.num_fmt, "general");
model.redo().unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.num_fmt, "$#,##0.0000");
let send_queue = model.flush_send_queue();
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
model2.apply_external_diffs(&send_queue).unwrap();
let style = model2.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.num_fmt, "$#,##0.0000");
}
#[test]
fn basic_borders() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
model
.update_range_style(&range, "border.left", "thin,#F1F1F1")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::Thin,
color: Some("#F1F1F1".to_owned()),
})
);
model
.update_range_style(&range, "border.left", "thin,")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::Thin,
color: None,
})
);
model
.update_range_style(&range, "border.right", "dotted,#F1F1F2")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.right,
Some(BorderItem {
style: BorderStyle::Dotted,
color: Some("#F1F1F2".to_owned()),
})
);
model
.update_range_style(&range, "border.top", "double,#F1F1F3")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.top,
Some(BorderItem {
style: BorderStyle::Double,
color: Some("#F1F1F3".to_owned()),
})
);
model
.update_range_style(&range, "border.bottom", "medium,#F1F1F4")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.bottom,
Some(BorderItem {
style: BorderStyle::Medium,
color: Some("#F1F1F4".to_owned()),
})
);
while model.can_undo() {
model.undo().unwrap();
}
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.border.left, None);
assert_eq!(style.border.top, None);
assert_eq!(style.border.right, None);
assert_eq!(style.border.bottom, None);
while model.can_redo() {
model.redo().unwrap();
}
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::Thin,
color: None,
})
);
assert_eq!(
style.border.right,
Some(BorderItem {
style: BorderStyle::Dotted,
color: Some("#F1F1F2".to_owned()),
})
);
assert_eq!(
style.border.top,
Some(BorderItem {
style: BorderStyle::Double,
color: Some("#F1F1F3".to_owned()),
})
);
assert_eq!(
style.border.bottom,
Some(BorderItem {
style: BorderStyle::Medium,
color: Some("#F1F1F4".to_owned()),
})
);
let send_queue = model.flush_send_queue();
let mut model2 = UserModel::new_empty("model", "en", "UTC").unwrap();
model2.apply_external_diffs(&send_queue).unwrap();
let style = model2.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::Thin,
color: None,
})
);
assert_eq!(
style.border.right,
Some(BorderItem {
style: BorderStyle::Dotted,
color: Some("#F1F1F2".to_owned()),
})
);
assert_eq!(
style.border.top,
Some(BorderItem {
style: BorderStyle::Double,
color: Some("#F1F1F3".to_owned()),
})
);
assert_eq!(
style.border.bottom,
Some(BorderItem {
style: BorderStyle::Medium,
color: Some("#F1F1F4".to_owned()),
})
);
}
#[test]
fn basic_alignment() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
assert_eq!(alignment, None);
model
.update_range_style(&range, "alignment.horizontal", "center")
.unwrap();
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
assert_eq!(
alignment,
Some(Alignment {
horizontal: HorizontalAlignment::Center,
vertical: VerticalAlignment::Bottom,
wrap_text: false
})
);
model
.update_range_style(&range, "alignment.horizontal", "centerContinuous")
.unwrap();
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
assert_eq!(
alignment,
Some(Alignment {
horizontal: HorizontalAlignment::CenterContinuous,
vertical: VerticalAlignment::Bottom,
wrap_text: false
})
);
let range = Area {
sheet: 0,
row: 2,
column: 2,
width: 1,
height: 1,
};
model
.update_range_style(&range, "alignment.vertical", "distributed")
.unwrap();
let alignment = model.get_cell_style(0, 2, 2).unwrap().alignment;
assert_eq!(
alignment,
Some(Alignment {
horizontal: HorizontalAlignment::General,
vertical: VerticalAlignment::Distributed,
wrap_text: false
})
);
model
.update_range_style(&range, "alignment.vertical", "justify")
.unwrap();
let alignment = model.get_cell_style(0, 2, 2).unwrap().alignment;
assert_eq!(
alignment,
Some(Alignment {
horizontal: HorizontalAlignment::General,
vertical: VerticalAlignment::Justify,
wrap_text: false
})
);
model.update_range_style(&range, "alignment", "").unwrap();
let alignment = model.get_cell_style(0, 2, 2).unwrap().alignment;
assert_eq!(alignment, None);
model.undo().unwrap();
let alignment = model.get_cell_style(0, 2, 2).unwrap().alignment;
assert_eq!(
alignment,
Some(Alignment {
horizontal: HorizontalAlignment::General,
vertical: VerticalAlignment::Justify,
wrap_text: false
})
);
}
#[test]
fn alignment_errors() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
assert_eq!(
model.update_range_style(&range, "alignment", "some"),
Err("Alignment must be empty, but found: 'some'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "alignment.vertical", "justified"),
Err("Invalid value for vertical alignment: 'justified'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "alignment.horizontal", "unjustified"),
Err("Invalid value for horizontal alignment: 'unjustified'.".to_string())
);
model
.update_range_style(&range, "alignment.vertical", "justify")
.unwrap();
// Also fail if there is an alignment
assert_eq!(
model.update_range_style(&range, "alignment", "some"),
Err("Alignment must be empty, but found: 'some'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "alignment.vertical", "justified"),
Err("Invalid value for vertical alignment: 'justified'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "alignment.horizontal", "unjustified"),
Err("Invalid value for horizontal alignment: 'unjustified'.".to_string())
);
}
#[test]
fn basic_wrap_text() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
assert_eq!(
model.update_range_style(&range, "alignment.wrap_text", "T"),
Err("Invalid value for boolean: 'T'.".to_string())
);
model
.update_range_style(&range, "alignment.wrap_text", "true")
.unwrap();
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
assert_eq!(
alignment,
Some(Alignment {
horizontal: HorizontalAlignment::General,
vertical: VerticalAlignment::Bottom,
wrap_text: true
})
);
model.undo().unwrap();
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
assert_eq!(alignment, None);
model.redo().unwrap();
let alignment = model.get_cell_style(0, 1, 1).unwrap().alignment;
assert_eq!(
alignment,
Some(Alignment {
horizontal: HorizontalAlignment::General,
vertical: VerticalAlignment::Bottom,
wrap_text: true
})
);
assert_eq!(
model.update_range_style(&range, "alignment.wrap_text", "True"),
Err("Invalid value for boolean: 'True'.".to_string())
);
}
#[test]
fn more_basic_borders() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
model
.update_range_style(&range, "border.left", "thick,#F1F1F1")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::Thick,
color: Some("#F1F1F1".to_owned()),
})
);
model
.update_range_style(&range, "border.left", "slantDashDot,#F1F1F1")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::SlantDashDot,
color: Some("#F1F1F1".to_owned()),
})
);
model
.update_range_style(&range, "border.left", "mediumDashDot,#F1F1F1")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::MediumDashDot,
color: Some("#F1F1F1".to_owned()),
})
);
model
.update_range_style(&range, "border.left", "mediumDashDotDot,#F1F1F1")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::MediumDashDotDot,
color: Some("#F1F1F1".to_owned()),
})
);
model
.update_range_style(&range, "border.left", "mediumDashed,#F1F1F1")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::MediumDashed,
color: Some("#F1F1F1".to_owned()),
})
);
}
#[test]
fn border_errors() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
assert_eq!(
model.update_range_style(&range, "border.lef", "thick,#F1F1F1"),
Err("Invalid style path: 'border.lef'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "border.left", "thic,#F1F1F1"),
Err("Invalid border style: 'thic'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "border.left", "thick,#F1F1F"),
Err("Invalid color: '#F1F1F'.".to_string())
);
assert_eq!(
model.update_range_style(&range, "border.left", " "),
Err("Invalid border value: ' '.".to_string())
);
assert_eq!(
model.update_range_style(&range, "border.left", "thick,#F1F1F1,thin"),
Err("Invalid border value: 'thick,#F1F1F1,thin'.".to_string())
);
}
#[test]
fn empty_removes_border() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
model
.update_range_style(&range, "border.left", "mediumDashDotDot,#F1F1F1")
.unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(
style.border.left,
Some(BorderItem {
style: BorderStyle::MediumDashDotDot,
color: Some("#F1F1F1".to_owned()),
})
);
model.update_range_style(&range, "border.left", "").unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert_eq!(style.border.left, None);
}
#[test]
fn false_removes_value() {
let mut model = UserModel::new_empty("model", "en", "UTC").unwrap();
let range = Area {
sheet: 0,
row: 1,
column: 1,
width: 1,
height: 1,
};
// bold
model.update_range_style(&range, "font.b", "true").unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(style.font.b);
model.update_range_style(&range, "font.b", "false").unwrap();
let style = model.get_cell_style(0, 1, 1).unwrap();
assert!(!style.font.b);
}

View File

@@ -0,0 +1,66 @@
#![allow(clippy::unwrap_used)]
use crate::{test::util::new_empty_model, UserModel};
#[test]
fn simple_undo_redo() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
// at the beginning I cannot undo or redo
assert!(!model.can_undo());
assert!(!model.can_redo());
assert!(model.set_user_input(0, 1, 1, "=1+2").is_ok());
// Once I enter a value I can undo but not redo
assert!(model.can_undo());
assert!(!model.can_redo());
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("3".to_string()));
// If I undo, I can't undo anymore, but I can redo
assert!(model.undo().is_ok());
assert!(!model.can_undo());
assert!(model.can_redo());
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("".to_string()));
// If I redo, I have the old value and formula
assert!(model.redo().is_ok());
assert_eq!(model.get_formatted_cell_value(0, 1, 1), Ok("3".to_string()));
assert_eq!(model.get_cell_content(0, 1, 1), Ok("=1+2".to_string()));
assert!(model.can_undo());
assert!(!model.can_redo());
}
#[test]
fn undo_redo_respect_styles() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
assert!(model.set_user_input(0, 1, 1, "100").is_ok());
assert!(model.set_user_input(0, 1, 1, "125$").is_ok());
// The content of the cell is just the number 125
assert_eq!(model.get_cell_content(0, 1, 1), Ok("125".to_string()));
assert!(model.undo().is_ok());
// The cell has no currency number formatting
assert_eq!(
model.get_formatted_cell_value(0, 1, 1),
Ok("100".to_string())
);
assert_eq!(model.get_cell_content(0, 1, 1), Ok("100".to_string()));
assert!(model.redo().is_ok());
// The cell has the number 125 formatted as '125$'
assert_eq!(
model.get_formatted_cell_value(0, 1, 1),
Ok("125$".to_string())
);
assert_eq!(model.get_cell_content(0, 1, 1), Ok("125".to_string()));
}
#[test]
fn can_undo_can_redo() {
let model = new_empty_model();
let mut model = UserModel::from_model(model);
assert!(!model.can_undo());
assert!(!model.can_redo());
assert!(model.undo().is_ok());
assert!(model.redo().is_ok());
}

View File

@@ -32,11 +32,11 @@ impl Model {
let cell_reference = self._parse_reference(cell);
let column = cell_reference.column;
let row = cell_reference.row;
self.cell_formula(cell_reference.sheet, row, column)
self.get_cell_formula(cell_reference.sheet, row, column)
.unwrap()
}
pub fn _get_text_at(&self, sheet: u32, row: i32, column: i32) -> String {
self.formatted_cell_value(sheet, row, column).unwrap()
self.get_formatted_cell_value(sheet, row, column).unwrap()
}
pub fn _get_text(&self, cell: &str) -> String {
let CellReferenceIndex { sheet, row, column } = self._parse_reference(cell);

1183
base/src/user_model.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -27,16 +27,4 @@ impl Workbook {
.get_mut(worksheet_index as usize)
.ok_or_else(|| "Invalid sheet index".to_string())
}
pub fn get_worksheets_info(&self) -> Vec<SheetInfo> {
self.worksheets
.iter()
.map(|worksheet| SheetInfo {
name: worksheet.get_name(),
state: worksheet.state.to_string(),
color: worksheet.color.clone(),
sheet_id: worksheet.sheet_id,
})
.collect()
}
}

View File

@@ -42,7 +42,7 @@ impl Worksheet {
self.sheet_data.get_mut(&row)?.get_mut(&column)
}
fn update_cell(&mut self, row: i32, column: i32, new_cell: Cell) {
pub(crate) fn update_cell(&mut self, row: i32, column: i32, new_cell: Cell) {
match self.sheet_data.get_mut(&row) {
Some(column_data) => match column_data.get(&column) {
Some(_cell) => {
@@ -68,9 +68,8 @@ impl Worksheet {
if row.r == row_index {
if row.custom_format {
return row.s;
} else {
break;
}
break;
}
}
let cols = &self.cols;
@@ -106,64 +105,8 @@ impl Worksheet {
}
pub fn set_column_style(&mut self, column: i32, style_index: i32) -> Result<(), String> {
let cols = &mut self.cols;
let col = Col {
min: column,
max: column,
width: constants::DEFAULT_COLUMN_WIDTH / constants::COLUMN_WIDTH_FACTOR,
custom_width: true,
style: Some(style_index),
};
let mut index = 0;
let mut split = false;
for c in cols.iter_mut() {
let min = c.min;
let max = c.max;
if min <= column && column <= max {
if min == column && max == column {
c.style = Some(style_index);
return Ok(());
} else {
// We need to split the result
split = true;
break;
}
}
if column < min {
// We passed, we should insert at index
break;
}
index += 1;
}
if split {
let min = cols[index].min;
let max = cols[index].max;
let pre = Col {
min,
max: column - 1,
width: cols[index].width,
custom_width: cols[index].custom_width,
style: cols[index].style,
};
let post = Col {
min: column + 1,
max,
width: cols[index].width,
custom_width: cols[index].custom_width,
style: cols[index].style,
};
cols.remove(index);
if column != max {
cols.insert(index, post);
}
cols.insert(index, col);
if column != min {
cols.insert(index, pre);
}
} else {
cols.insert(index, col);
}
Ok(())
let width = constants::DEFAULT_COLUMN_WIDTH / constants::COLUMN_WIDTH_FACTOR;
self.set_column_width_and_style(column, width, Some(style_index))
}
pub fn set_row_style(&mut self, row: i32, style_index: i32) -> Result<(), String> {
@@ -191,7 +134,7 @@ impl Worksheet {
cell.set_style(style_index);
}
None => {
self.set_cell_empty_with_style(row, column, style_index);
self.cell_clear_contents_with_style(row, column, style_index);
}
}
@@ -223,13 +166,13 @@ impl Worksheet {
self.update_cell(row, column, cell);
}
pub fn set_cell_empty(&mut self, row: i32, column: i32) {
pub fn cell_clear_contents(&mut self, row: i32, column: i32) {
let s = self.get_style(row, column);
let cell = Cell::EmptyCell { s };
self.update_cell(row, column, cell);
}
pub fn set_cell_empty_with_style(&mut self, row: i32, column: i32, style: i32) {
pub fn cell_clear_contents_with_style(&mut self, row: i32, column: i32, style: i32) {
let cell = Cell::EmptyCell { s: style };
self.update_cell(row, column, cell);
}
@@ -237,7 +180,8 @@ impl Worksheet {
pub fn set_frozen_rows(&mut self, frozen_rows: i32) -> Result<(), String> {
if frozen_rows < 0 {
return Err("Frozen rows cannot be negative".to_string());
} else if frozen_rows >= constants::LAST_ROW {
}
if frozen_rows >= constants::LAST_ROW {
return Err("Too many rows".to_string());
}
self.frozen_rows = frozen_rows;
@@ -247,7 +191,8 @@ impl Worksheet {
pub fn set_frozen_columns(&mut self, frozen_columns: i32) -> Result<(), String> {
if frozen_columns < 0 {
return Err("Frozen columns cannot be negative".to_string());
} else if frozen_columns >= constants::LAST_COLUMN {
}
if frozen_columns >= constants::LAST_COLUMN {
return Err("Too many columns".to_string());
}
self.frozen_columns = frozen_columns;
@@ -281,11 +226,21 @@ impl Worksheet {
});
Ok(())
}
/// Changes the width of a column.
/// * If the column does not a have a width we simply add it
/// * If it has, it might be part of a range and we ned to split the range.
/// Fails if column index is outside allowed range.
pub fn set_column_width(&mut self, column: i32, width: f64) -> Result<(), String> {
self.set_column_width_and_style(column, width, None)
}
pub(crate) fn set_column_width_and_style(
&mut self,
column: i32,
width: f64,
style: Option<i32>,
) -> Result<(), String> {
if !is_valid_column_number(column) {
return Err(format!("Column number '{column}' is not valid."));
}
@@ -295,7 +250,7 @@ impl Worksheet {
max: column,
width: width / constants::COLUMN_WIDTH_FACTOR,
custom_width: true,
style: None,
style,
};
let mut index = 0;
let mut split = false;
@@ -306,12 +261,10 @@ impl Worksheet {
if min == column && max == column {
c.width = width / constants::COLUMN_WIDTH_FACTOR;
return Ok(());
} else {
// We need to split the result
}
split = true;
break;
}
}
if column < min {
// We passed, we should insert at index
break;
@@ -351,7 +304,7 @@ impl Worksheet {
}
/// Return the width of a column in pixels
pub fn column_width(&self, column: i32) -> Result<f64, String> {
pub fn get_column_width(&self, column: i32) -> Result<f64, String> {
if !is_valid_column_number(column) {
return Err(format!("Column number '{column}' is not valid."));
}
@@ -363,9 +316,8 @@ impl Worksheet {
if column >= min && column <= max {
if col.custom_width {
return Ok(col.width * constants::COLUMN_WIDTH_FACTOR);
} else {
break;
}
break;
}
}
Ok(constants::DEFAULT_COLUMN_WIDTH)

View File

@@ -1,5 +1,5 @@
use ironcalc::{
base::{expressions::utils::number_to_column, model::Model},
base::{expressions::utils::number_to_column, Model},
export::save_to_xlsx,
};

View File

@@ -1,4 +1,4 @@
use ironcalc::{base::model::Model, export::save_to_xlsx};
use ironcalc::{base::Model, export::save_to_xlsx};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut model = Model::new_empty("hello_styles", "en", "UTC")?;

View File

@@ -1,4 +1,4 @@
use ironcalc::{base::model::Model, export::save_to_xlsx};
use ironcalc::{base::Model, export::save_to_xlsx};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut model = Model::new_empty("widths-and-heights", "en", "UTC")?;
@@ -6,7 +6,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let (sheet, row, column) = (0, 5, 3);
// Make the first column 4 times as width
let worksheet = model.workbook.worksheet_mut(sheet)?;
let column_width = worksheet.column_width(column)? * 4.0;
let column_width = worksheet.get_column_width(column)? * 4.0;
worksheet.set_column_width(column, column_width)?;
// and the first row twice as high.

View File

@@ -5,7 +5,7 @@
use std::fs;
use ironcalc_base::model::Model;
use ironcalc_base::Model;
fn main() {
let args: Vec<_> = std::env::args().collect();

View File

@@ -2,7 +2,7 @@ use std::path::Path;
use ironcalc_base::cell::CellValue;
use ironcalc_base::types::*;
use ironcalc_base::{expressions::utils::number_to_column, model::Model};
use ironcalc_base::{expressions::utils::number_to_column, Model};
use crate::export::save_to_xlsx;
use crate::import::load_model_from_xlsx;
@@ -208,7 +208,7 @@ pub fn test_load_and_saving(file_path: &str, temp_dir_name: &Path) -> Result<(),
#[cfg(test)]
mod tests {
use crate::compare::compare;
use ironcalc_base::model::Model;
use ironcalc_base::Model;
#[test]
fn compare_different_sheets() {

View File

@@ -15,8 +15,8 @@ use std::{
};
use ironcalc_base::expressions::utils::number_to_column;
use ironcalc_base::model::{get_milliseconds_since_epoch, Model};
use ironcalc_base::types::Workbook;
use ironcalc_base::{get_milliseconds_since_epoch, Model};
use self::xml_constants::XML_DECLARATION;

View File

@@ -1,6 +1,6 @@
use std::fs;
use ironcalc_base::model::Model;
use ironcalc_base::Model;
use crate::error::XlsxError;
use crate::{export::save_to_xlsx, import::load_model_from_xlsx};
@@ -31,13 +31,22 @@ fn test_values() {
save_to_xlsx(&model, temp_file_name).unwrap();
let model = load_model_from_xlsx(temp_file_name, "en", "UTC").unwrap();
assert_eq!(model.formatted_cell_value(0, 1, 1).unwrap(), "123.456");
assert_eq!(model.formatted_cell_value(0, 2, 1).unwrap(), "Hello world!");
assert_eq!(model.formatted_cell_value(0, 3, 1).unwrap(), "Hello world!");
assert_eq!(model.formatted_cell_value(0, 4, 1).unwrap(), "你好世界!");
assert_eq!(model.formatted_cell_value(0, 5, 1).unwrap(), "TRUE");
assert_eq!(model.formatted_cell_value(0, 6, 1).unwrap(), "FALSE");
assert_eq!(model.formatted_cell_value(0, 7, 1).unwrap(), "#VALUE!");
assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "123.456");
assert_eq!(
model.get_formatted_cell_value(0, 2, 1).unwrap(),
"Hello world!"
);
assert_eq!(
model.get_formatted_cell_value(0, 3, 1).unwrap(),
"Hello world!"
);
assert_eq!(
model.get_formatted_cell_value(0, 4, 1).unwrap(),
"你好世界!"
);
assert_eq!(model.get_formatted_cell_value(0, 5, 1).unwrap(), "TRUE");
assert_eq!(model.get_formatted_cell_value(0, 6, 1).unwrap(), "FALSE");
assert_eq!(model.get_formatted_cell_value(0, 7, 1).unwrap(), "#VALUE!");
fs::remove_file(temp_file_name).unwrap();
}
@@ -59,10 +68,10 @@ fn test_formulas() {
save_to_xlsx(&model, temp_file_name).unwrap();
let model = load_model_from_xlsx(temp_file_name, "en", "UTC").unwrap();
assert_eq!(model.formatted_cell_value(0, 1, 2).unwrap(), "11");
assert_eq!(model.formatted_cell_value(0, 2, 2).unwrap(), "13");
assert_eq!(model.formatted_cell_value(0, 3, 2).unwrap(), "15");
assert_eq!(model.formatted_cell_value(0, 4, 2).unwrap(), "58.5");
assert_eq!(model.get_formatted_cell_value(0, 1, 2).unwrap(), "11");
assert_eq!(model.get_formatted_cell_value(0, 2, 2).unwrap(), "13");
assert_eq!(model.get_formatted_cell_value(0, 3, 2).unwrap(), "15");
assert_eq!(model.get_formatted_cell_value(0, 4, 2).unwrap(), "58.5");
fs::remove_file(temp_file_name).unwrap();
}

View File

@@ -16,8 +16,8 @@ use std::{
use roxmltree::Node;
use ironcalc_base::{
model::Model,
types::{Metadata, Workbook, WorkbookSettings},
Model,
};
use crate::error::XlsxError;

View File

@@ -1,7 +1,7 @@
//! # IronCalc - Core API documentation
//!
//! This technical API documentation is aimed at developers.
//! It is used to build language bindings (like python, javascript or nodejs) or to build full fledged applications like ironCalc in the terminal or IronCalc, the Web application.
//! It is used to build language bindings (like python, javascript or nodejs) or to build full fledged applications like TironCalc in the terminal or IronCalc, the Web application.
//!
//! ## Basic usage
//!
@@ -9,7 +9,7 @@
//!
//! ```toml
//! [dependencies]
//! ironcalc = { git = "https://github.com/ironcalc/IronCalc", version = "0.1"}
//! ironcalc = { git = "https://github.com/ironcalc/IronCalc" }
//! ```
//!
//! <small> until version 0.5.0 you should use the git dependencies as stated </small>

View File

@@ -4,8 +4,8 @@ use uuid::Uuid;
use ironcalc::compare::{test_file, test_load_and_saving};
use ironcalc::export::save_to_xlsx;
use ironcalc::import::{load_from_excel, load_model_from_xlsx};
use ironcalc_base::model::Model;
use ironcalc_base::types::{HorizontalAlignment, VerticalAlignment, Workbook};
use ironcalc_base::Model;
// This is a functional test.
// We check that the output of example.xlsx is what we expect.
@@ -179,7 +179,7 @@ fn test_defined_names_casing() {
model.set_user_input(0, row, column, formula.to_string());
model.evaluate();
assert_eq!(
model.formatted_cell_value(0, row, column).unwrap(),
model.get_formatted_cell_value(0, row, column).unwrap(),
expected_value
);
}