1pub mod cell;
2pub mod error;
3pub mod sheet;
4pub mod style;
5pub mod types;
6pub mod workbook;
7
8pub use error::{ExcelError, ExcelResult};
9pub use types::*;
10pub use workbook::Workbook;
11
12#[cfg(test)]
13mod tests {
14 use super::*;
15
16 #[test]
17 fn workbook_round_trip_bytes() {
18 let mut wb = Workbook::new();
19 let ws = wb.get_sheet_mut("Sheet1").unwrap();
20 sheet::set_cell_value(ws, 1, 1, &CellValue::String("hello".into()));
21 sheet::set_cell_value(ws, 1, 2, &CellValue::Number(42.0));
22
23 let bytes = wb.save_to_bytes().unwrap();
24 assert!(!bytes.is_empty());
25
26 let wb2 = Workbook::open_from_bytes(&bytes).unwrap();
27 let ws2 = wb2.get_sheet("Sheet1").unwrap();
28 assert_eq!(
29 sheet::get_cell_value(ws2, 1, 1),
30 CellValue::String("hello".into())
31 );
32 assert_eq!(sheet::get_cell_value(ws2, 1, 2), CellValue::Number(42.0));
33 }
34
35 #[test]
36 fn cell_value_types() {
37 let mut wb = Workbook::new();
38 let ws = wb.get_sheet_mut("Sheet1").unwrap();
39
40 sheet::set_cell_value(ws, 1, 1, &CellValue::String("text".into()));
41 sheet::set_cell_value(ws, 2, 1, &CellValue::Number(3.14));
42 sheet::set_cell_value(ws, 3, 1, &CellValue::Boolean(true));
43 sheet::set_cell_value(ws, 4, 1, &CellValue::Formula("=1+1".into()));
44 sheet::set_cell_value(ws, 5, 1, &CellValue::Empty);
45
46 assert_eq!(
47 sheet::get_cell_value(ws, 1, 1),
48 CellValue::String("text".into())
49 );
50 assert_eq!(sheet::get_cell_value(ws, 2, 1), CellValue::Number(3.14));
51 assert_eq!(sheet::get_cell_value(ws, 3, 1), CellValue::Boolean(true));
52 assert_eq!(
53 sheet::get_cell_value(ws, 4, 1),
54 CellValue::Formula("=1+1".into())
55 );
56 assert_eq!(sheet::get_cell_value(ws, 5, 1), CellValue::Empty);
57 }
58
59 #[test]
60 fn sheet_management() {
61 let mut wb = Workbook::new();
62 assert_eq!(wb.sheet_names(), vec!["Sheet1"]);
63
64 wb.add_sheet("Data").unwrap();
65 assert_eq!(wb.sheet_names().len(), 2);
66 assert!(wb.sheet_names().contains(&"Data".to_string()));
67
68 wb.rename_sheet("Data", "MyData").unwrap();
69 assert!(wb.sheet_names().contains(&"MyData".to_string()));
70 assert!(!wb.sheet_names().contains(&"Data".to_string()));
71
72 wb.remove_sheet("MyData").unwrap();
73 assert_eq!(wb.sheet_names(), vec!["Sheet1"]);
74 }
75
76 #[test]
77 fn sheet_already_exists_error() {
78 let mut wb = Workbook::new();
79 let result = wb.add_sheet("Sheet1");
80 assert!(result.is_err());
81 assert!(matches!(
82 result.unwrap_err(),
83 ExcelError::SheetAlreadyExists(_)
84 ));
85 }
86
87 #[test]
88 fn sheet_not_found_error() {
89 let wb = Workbook::new();
90 let result = wb.get_sheet("Nonexistent");
91 assert!(result.is_err());
92 assert!(matches!(result.unwrap_err(), ExcelError::SheetNotFound(_)));
93 }
94
95 #[test]
96 fn range_read_write() {
97 let mut wb = Workbook::new();
98 let ws = wb.get_sheet_mut("Sheet1").unwrap();
99
100 let data = RangeData {
101 start_row: 1,
102 start_col: 1,
103 end_row: 3,
104 end_col: 3,
105 rows: vec![
106 vec![
107 CellValue::Number(1.0),
108 CellValue::Number(2.0),
109 CellValue::Number(3.0),
110 ],
111 vec![
112 CellValue::Number(4.0),
113 CellValue::Number(5.0),
114 CellValue::Number(6.0),
115 ],
116 vec![
117 CellValue::Number(7.0),
118 CellValue::Number(8.0),
119 CellValue::Number(9.0),
120 ],
121 ],
122 };
123
124 sheet::write_range(ws, &data);
125 let result = sheet::read_range(ws, 1, 1, 3, 3);
126
127 assert_eq!(result.rows.len(), 3);
128 assert_eq!(result.rows[0].len(), 3);
129 assert_eq!(result.rows[0][0], CellValue::Number(1.0));
130 assert_eq!(result.rows[1][1], CellValue::Number(5.0));
131 assert_eq!(result.rows[2][2], CellValue::Number(9.0));
132 }
133
134 #[test]
135 fn row_height_and_column_width() {
136 let mut wb = Workbook::new();
137 let ws = wb.get_sheet_mut("Sheet1").unwrap();
138
139 sheet::set_row_height(ws, 1, 30.0);
140 sheet::set_column_width(ws, 1, 20.0);
141
142 assert_eq!(sheet::get_row_height(ws, 1), 30.0);
143 assert_eq!(sheet::get_column_width(ws, 1), 20.0);
144 }
145
146 #[test]
147 fn merge_and_unmerge_cells() {
148 let mut wb = Workbook::new();
149 let ws = wb.get_sheet_mut("Sheet1").unwrap();
150
151 let range = MergeRange {
152 start_row: 1,
153 start_col: 1,
154 end_row: 3,
155 end_col: 3,
156 };
157 sheet::merge_cells(ws, &range);
158
159 let merges = sheet::get_merged_cells(ws);
160 assert_eq!(merges.len(), 1);
161 assert_eq!(merges[0].start_row, 1);
162 assert_eq!(merges[0].end_col, 3);
163
164 sheet::unmerge_cells(ws, &range);
165 let merges = sheet::get_merged_cells(ws);
166 assert_eq!(merges.len(), 0);
167 }
168
169 #[test]
170 fn style_round_trip() {
171 let mut wb = Workbook::new();
172 let ws = wb.get_sheet_mut("Sheet1").unwrap();
173
174 sheet::set_cell_value(ws, 1, 1, &CellValue::String("styled".into()));
176
177 let dto = CellStyleDto {
178 bold: Some(true),
179 font_size: Some(16.0),
180 font_name: Some("Arial".into()),
181 bg_color: Some("FFFF0000".into()),
182 ..Default::default()
183 };
184 style::apply_cell_style(ws, 1, 1, &dto);
185
186 let result = style::get_cell_style(ws, 1, 1);
187 assert_eq!(result.bold, Some(true));
188 assert_eq!(result.font_size, Some(16.0));
189 assert_eq!(result.font_name, Some("Arial".into()));
190 assert_eq!(result.bg_color, Some("FFFF0000".into()));
191 }
192
193 #[test]
194 fn cell_info_with_formula() {
195 let mut wb = Workbook::new();
196 let ws = wb.get_sheet_mut("Sheet1").unwrap();
197
198 sheet::set_cell_value(ws, 1, 1, &CellValue::Formula("=SUM(A2:A10)".into()));
199 let info = sheet::get_cell_info(ws, 1, 1);
200 assert_eq!(info.row, 1);
201 assert_eq!(info.col, 1);
202 assert_eq!(info.formula, Some("=SUM(A2:A10)".into()));
203 }
204
205 #[test]
206 fn workbook_info() {
207 let mut wb = Workbook::new();
208 wb.add_sheet("Second").unwrap();
209
210 let info = wb.info();
211 assert_eq!(info.sheet_count, 2);
212 assert_eq!(info.sheets[0].name, "Sheet1");
213 assert_eq!(info.sheets[1].name, "Second");
214 assert_eq!(info.sheets[0].index, 0);
215 assert_eq!(info.sheets[1].index, 1);
216 }
217
218 #[test]
219 fn cell_value_serialization() {
220 let val = CellValue::Number(42.0);
221 let json = serde_json::to_string(&val).unwrap();
222 let back: CellValue = serde_json::from_str(&json).unwrap();
223 assert_eq!(back, CellValue::Number(42.0));
224
225 let val = CellValue::String("hello".into());
226 let json = serde_json::to_string(&val).unwrap();
227 let back: CellValue = serde_json::from_str(&json).unwrap();
228 assert_eq!(back, CellValue::String("hello".into()));
229 }
230
231 #[test]
234 fn create_from_data_produces_valid_xlsx() {
235 let data = RangeData {
236 start_row: 1,
237 start_col: 1,
238 end_row: 2,
239 end_col: 3,
240 rows: vec![
241 vec![
242 CellValue::String("Name".into()),
243 CellValue::String("Age".into()),
244 CellValue::String("Active".into()),
245 ],
246 vec![
247 CellValue::String("Alice".into()),
248 CellValue::Number(30.0),
249 CellValue::Boolean(true),
250 ],
251 ],
252 };
253
254 let bytes = Workbook::create_from_data(vec![("People".into(), data)]).unwrap();
255 assert!(!bytes.is_empty());
256
257 let wb = Workbook::open_from_bytes(&bytes).unwrap();
259 assert!(wb.sheet_names().contains(&"People".to_string()));
260 }
261
262 #[test]
263 fn create_from_data_round_trip_via_calamine() {
264 let data = RangeData {
265 start_row: 1,
266 start_col: 1,
267 end_row: 2,
268 end_col: 2,
269 rows: vec![
270 vec![CellValue::Number(1.0), CellValue::Number(2.0)],
271 vec![CellValue::Number(3.0), CellValue::Number(4.0)],
272 ],
273 };
274
275 let bytes = Workbook::create_from_data(vec![("Data".into(), data)]).unwrap();
277
278 let wb = Workbook::open_from_bytes(&bytes).unwrap();
280 let result = wb.read_sheet_data("Data").unwrap();
281
282 assert_eq!(result.rows.len(), 2);
283 assert_eq!(result.rows[0][0], CellValue::Number(1.0));
284 assert_eq!(result.rows[0][1], CellValue::Number(2.0));
285 assert_eq!(result.rows[1][0], CellValue::Number(3.0));
286 assert_eq!(result.rows[1][1], CellValue::Number(4.0));
287 }
288
289 #[test]
290 fn read_sheet_data_fast() {
291 let mut wb = Workbook::new();
292 let ws = wb.get_sheet_mut("Sheet1").unwrap();
293 sheet::set_cell_value(ws, 1, 1, &CellValue::String("hello".into()));
294 sheet::set_cell_value(ws, 1, 2, &CellValue::Number(42.0));
295 sheet::set_cell_value(ws, 2, 1, &CellValue::Boolean(true));
296
297 let data = wb.read_sheet_data("Sheet1").unwrap();
298 assert!(!data.rows.is_empty());
299
300 assert_eq!(data.rows[0][0], CellValue::String("hello".into()));
303 assert_eq!(data.rows[0][1], CellValue::Number(42.0));
304 assert_eq!(data.rows[1][0], CellValue::Boolean(true));
305 }
306
307 #[test]
308 fn read_data_from_bytes_direct() {
309 let mut wb = Workbook::new();
310 let ws = wb.get_sheet_mut("Sheet1").unwrap();
311 sheet::set_cell_value(ws, 1, 1, &CellValue::String("fast".into()));
312 sheet::set_cell_value(ws, 1, 2, &CellValue::Number(99.0));
313 sheet::set_cell_value(ws, 2, 1, &CellValue::Boolean(false));
314
315 let bytes = wb.save_to_bytes().unwrap();
316
317 let data = Workbook::read_data_from_bytes(&bytes, "Sheet1").unwrap();
319 assert_eq!(data.rows[0][0], CellValue::String("fast".into()));
320 assert_eq!(data.rows[0][1], CellValue::Number(99.0));
321 assert_eq!(data.rows[1][0], CellValue::Boolean(false));
322 }
323
324 #[test]
325 fn read_data_from_bytes_sheet_not_found() {
326 let wb = Workbook::new();
327 let bytes = wb.save_to_bytes().unwrap();
328 let result = Workbook::read_data_from_bytes(&bytes, "NoSuchSheet");
329 assert!(result.is_err());
330 }
331
332 #[test]
333 fn read_sheet_data_not_found() {
334 let wb = Workbook::new();
335 let result = wb.read_sheet_data("NoSuchSheet");
336 assert!(result.is_err());
337 assert!(matches!(result.unwrap_err(), ExcelError::SheetNotFound(_)));
338 }
339
340 #[test]
341 fn create_from_data_multiple_sheets() {
342 let sheet1 = RangeData {
343 start_row: 1,
344 start_col: 1,
345 end_row: 1,
346 end_col: 1,
347 rows: vec![vec![CellValue::String("Sheet1 Data".into())]],
348 };
349 let sheet2 = RangeData {
350 start_row: 1,
351 start_col: 1,
352 end_row: 1,
353 end_col: 1,
354 rows: vec![vec![CellValue::String("Sheet2 Data".into())]],
355 };
356
357 let bytes =
358 Workbook::create_from_data(vec![("First".into(), sheet1), ("Second".into(), sheet2)])
359 .unwrap();
360
361 let wb = Workbook::open_from_bytes(&bytes).unwrap();
362 let names = wb.sheet_names();
363 assert!(names.contains(&"First".to_string()));
364 assert!(names.contains(&"Second".to_string()));
365 }
366
367 #[test]
370 fn open_from_bytes_cached_enables_fast_read() {
371 let mut wb = Workbook::new();
372 let ws = wb.get_sheet_mut("Sheet1").unwrap();
373 sheet::set_cell_value(ws, 1, 1, &CellValue::String("cached".into()));
374 sheet::set_cell_value(ws, 1, 2, &CellValue::Number(7.0));
375 let bytes = wb.save_to_bytes().unwrap();
376
377 let wb2 = Workbook::open_from_bytes_cached(bytes).unwrap();
379 assert!(wb2.has_cache());
380
381 let data = wb2.read_sheet_data("Sheet1").unwrap();
383 assert_eq!(data.rows[0][0], CellValue::String("cached".into()));
384 assert_eq!(data.rows[0][1], CellValue::Number(7.0));
385 }
386
387 #[test]
388 fn mark_dirty_invalidates_cache() {
389 let mut wb = Workbook::new();
390 let ws = wb.get_sheet_mut("Sheet1").unwrap();
391 sheet::set_cell_value(ws, 1, 1, &CellValue::String("before".into()));
392 let bytes = wb.save_to_bytes().unwrap();
393
394 let mut wb2 = Workbook::open_from_bytes_cached(bytes).unwrap();
395 assert!(wb2.has_cache());
396
397 wb2.mark_dirty();
398 assert!(!wb2.has_cache());
399
400 let data = wb2.read_sheet_data("Sheet1").unwrap();
402 assert_eq!(data.rows[0][0], CellValue::String("before".into()));
403 }
404
405 #[test]
406 fn save_to_bytes_fast_produces_valid_xlsx() {
407 let mut wb = Workbook::new();
408 let ws = wb.get_sheet_mut("Sheet1").unwrap();
409 sheet::set_cell_value(ws, 1, 1, &CellValue::String("fast write".into()));
410 sheet::set_cell_value(ws, 2, 1, &CellValue::Number(123.0));
411 sheet::set_cell_value(ws, 3, 1, &CellValue::Boolean(true));
412
413 let bytes = wb.save_to_bytes_fast().unwrap();
414 assert!(!bytes.is_empty());
415
416 let data = Workbook::read_data_from_bytes(&bytes, "Sheet1").unwrap();
418 assert_eq!(data.rows[0][0], CellValue::String("fast write".into()));
419 assert_eq!(data.rows[1][0], CellValue::Number(123.0));
420 assert_eq!(data.rows[2][0], CellValue::Boolean(true));
421 }
422
423 #[test]
424 fn save_to_bytes_fast_round_trip() {
425 let mut wb = Workbook::new();
426 wb.add_sheet("Data").unwrap();
427 let ws = wb.get_sheet_mut("Data").unwrap();
428 sheet::set_cell_value(ws, 1, 1, &CellValue::String("hello".into()));
429 sheet::set_cell_value(ws, 1, 2, &CellValue::Number(42.0));
430
431 let bytes = wb.save_to_bytes_fast().unwrap();
433 let wb2 = Workbook::open_from_bytes(&bytes).unwrap();
434 assert!(wb2.sheet_names().contains(&"Data".to_string()));
435
436 let ws2 = wb2.get_sheet("Data").unwrap();
437 assert_eq!(
438 sheet::get_cell_value(ws2, 1, 1),
439 CellValue::String("hello".into())
440 );
441 assert_eq!(sheet::get_cell_value(ws2, 1, 2), CellValue::Number(42.0));
442 }
443}