1use super::*;
2
3impl Workbook {
4 pub fn sheet_names(&self) -> Vec<&str> {
6 self.worksheets
7 .iter()
8 .map(|(name, _)| name.as_str())
9 .collect()
10 }
11
12 pub fn new_sheet(&mut self, name: &str) -> Result<usize> {
14 let idx = crate::sheet::add_sheet(
15 &mut self.workbook_xml,
16 &mut self.workbook_rels,
17 &mut self.content_types,
18 &mut self.worksheets,
19 name,
20 WorksheetXml::default(),
21 )?;
22 if self.sheet_comments.len() < self.worksheets.len() {
23 self.sheet_comments.push(None);
24 }
25 if self.sheet_sparklines.len() < self.worksheets.len() {
26 self.sheet_sparklines.push(vec![]);
27 }
28 if self.sheet_vml.len() < self.worksheets.len() {
29 self.sheet_vml.push(None);
30 }
31 if self.raw_sheet_xml.len() < self.worksheets.len() {
32 self.raw_sheet_xml.push(None);
33 }
34 if self.sheet_dirty.len() < self.worksheets.len() {
35 self.sheet_dirty.push(true);
36 }
37 if self.sheet_threaded_comments.len() < self.worksheets.len() {
38 self.sheet_threaded_comments.push(None);
39 }
40 if self.sheet_form_controls.len() < self.worksheets.len() {
41 self.sheet_form_controls.push(vec![]);
42 }
43 self.rebuild_sheet_index();
44 Ok(idx)
45 }
46
47 pub fn delete_sheet(&mut self, name: &str) -> Result<()> {
49 let idx = self.sheet_index(name)?;
50 self.assert_parallel_vecs_in_sync();
51
52 crate::sheet::delete_sheet(
53 &mut self.workbook_xml,
54 &mut self.workbook_rels,
55 &mut self.content_types,
56 &mut self.worksheets,
57 name,
58 )?;
59
60 self.sheet_comments.remove(idx);
64 self.sheet_sparklines.remove(idx);
65 self.sheet_vml.remove(idx);
66 self.raw_sheet_xml.remove(idx);
67 self.sheet_dirty.remove(idx);
68 self.sheet_threaded_comments.remove(idx);
69 self.sheet_form_controls.remove(idx);
70
71 self.tables.retain(|(_, _, si)| *si != idx);
73 for (_, _, si) in &mut self.tables {
74 if *si > idx {
75 *si -= 1;
76 }
77 }
78
79 self.streamed_sheets.remove(&idx);
81 self.streamed_sheets = self
82 .streamed_sheets
83 .drain()
84 .map(|(i, data)| if i > idx { (i - 1, data) } else { (i, data) })
85 .collect();
86
87 self.reindex_sheet_maps_after_delete(idx);
88 self.rebuild_sheet_index();
89 Ok(())
90 }
91
92 fn assert_parallel_vecs_in_sync(&self) {
96 let n = self.worksheets.len();
97 debug_assert_eq!(self.sheet_comments.len(), n, "sheet_comments desync");
98 debug_assert_eq!(self.sheet_sparklines.len(), n, "sheet_sparklines desync");
99 debug_assert_eq!(self.sheet_vml.len(), n, "sheet_vml desync");
100 debug_assert_eq!(self.raw_sheet_xml.len(), n, "raw_sheet_xml desync");
101 debug_assert_eq!(self.sheet_dirty.len(), n, "sheet_dirty desync");
102 debug_assert_eq!(
103 self.sheet_threaded_comments.len(),
104 n,
105 "sheet_threaded_comments desync"
106 );
107 debug_assert_eq!(
108 self.sheet_form_controls.len(),
109 n,
110 "sheet_form_controls desync"
111 );
112 }
113
114 pub fn set_sheet_name(&mut self, old_name: &str, new_name: &str) -> Result<()> {
116 crate::sheet::rename_sheet(
117 &mut self.workbook_xml,
118 &mut self.worksheets,
119 old_name,
120 new_name,
121 )?;
122 self.rebuild_sheet_index();
123 Ok(())
124 }
125
126 pub fn copy_sheet(&mut self, source: &str, target: &str) -> Result<usize> {
128 let src_idx = self.sheet_index(source)?;
130 self.ensure_hydrated(src_idx)?;
133 let idx = crate::sheet::copy_sheet(
134 &mut self.workbook_xml,
135 &mut self.workbook_rels,
136 &mut self.content_types,
137 &mut self.worksheets,
138 source,
139 target,
140 )?;
141 if self.sheet_comments.len() < self.worksheets.len() {
142 self.sheet_comments.push(None);
143 }
144 let source_sparklines = self
145 .sheet_sparklines
146 .get(src_idx)
147 .cloned()
148 .unwrap_or_default();
149 if self.sheet_sparklines.len() < self.worksheets.len() {
150 self.sheet_sparklines.push(source_sparklines);
151 }
152 if self.sheet_vml.len() < self.worksheets.len() {
153 self.sheet_vml.push(None);
154 }
155 if self.raw_sheet_xml.len() < self.worksheets.len() {
156 self.raw_sheet_xml.push(None);
157 }
158 if self.sheet_dirty.len() < self.worksheets.len() {
159 self.sheet_dirty.push(true);
160 }
161 if self.sheet_threaded_comments.len() < self.worksheets.len() {
162 self.sheet_threaded_comments.push(None);
163 }
164 if self.sheet_form_controls.len() < self.worksheets.len() {
165 self.sheet_form_controls.push(vec![]);
166 }
167 if let Some(src_streamed) = self.streamed_sheets.get(&src_idx) {
169 let cloned = src_streamed.try_clone()?;
170 self.streamed_sheets.insert(idx, cloned);
171 }
172 self.rebuild_sheet_index();
173 Ok(idx)
174 }
175
176 pub fn get_sheet_index(&self, name: &str) -> Option<usize> {
178 crate::sheet::find_sheet_index(&self.worksheets, name)
179 }
180
181 pub fn get_active_sheet(&self) -> &str {
183 let idx = crate::sheet::active_sheet_index(&self.workbook_xml);
184 self.worksheets
185 .get(idx)
186 .map(|(n, _)| n.as_str())
187 .unwrap_or_else(|| self.worksheets[0].0.as_str())
188 }
189
190 pub fn set_active_sheet(&mut self, name: &str) -> Result<()> {
192 let idx = crate::sheet::find_sheet_index(&self.worksheets, name).ok_or_else(|| {
193 Error::SheetNotFound {
194 name: name.to_string(),
195 }
196 })?;
197 crate::sheet::set_active_sheet_index(&mut self.workbook_xml, idx as u32);
198 Ok(())
199 }
200
201 pub fn new_stream_writer(&self, sheet_name: &str) -> Result<crate::stream::StreamWriter> {
206 crate::sheet::validate_sheet_name(sheet_name)?;
207 if self.worksheets.iter().any(|(n, _)| n == sheet_name) {
208 return Err(Error::SheetAlreadyExists {
209 name: sheet_name.to_string(),
210 });
211 }
212 Ok(crate::stream::StreamWriter::new(sheet_name))
213 }
214
215 pub fn apply_stream_writer(&mut self, writer: crate::stream::StreamWriter) -> Result<usize> {
228 let (sheet_name, streamed_data) = writer.into_streamed_data()?;
229
230 let idx = crate::sheet::add_sheet(
234 &mut self.workbook_xml,
235 &mut self.workbook_rels,
236 &mut self.content_types,
237 &mut self.worksheets,
238 &sheet_name,
239 WorksheetXml::default(),
240 )?;
241 if self.sheet_comments.len() < self.worksheets.len() {
242 self.sheet_comments.push(None);
243 }
244 if self.sheet_sparklines.len() < self.worksheets.len() {
245 self.sheet_sparklines.push(vec![]);
246 }
247 if self.sheet_vml.len() < self.worksheets.len() {
248 self.sheet_vml.push(None);
249 }
250 if self.raw_sheet_xml.len() < self.worksheets.len() {
251 self.raw_sheet_xml.push(None);
252 }
253 if self.sheet_dirty.len() < self.worksheets.len() {
254 self.sheet_dirty.push(true);
255 }
256 if self.sheet_threaded_comments.len() < self.worksheets.len() {
257 self.sheet_threaded_comments.push(None);
258 }
259 if self.sheet_form_controls.len() < self.worksheets.len() {
260 self.sheet_form_controls.push(vec![]);
261 }
262
263 self.streamed_sheets.insert(idx, streamed_data);
265
266 self.rebuild_sheet_index();
267 Ok(idx)
268 }
269
270 pub fn insert_rows(&mut self, sheet: &str, start_row: u32, count: u32) -> Result<()> {
272 let sheet_idx = self.sheet_index(sheet)?;
273 {
274 let ws = self.worksheet_mut_by_index(sheet_idx)?;
275 crate::row::insert_rows(ws, start_row, count)?;
276 }
277 self.apply_reference_shift_for_sheet(sheet_idx, |col, row| {
278 if row >= start_row {
279 (col, row + count)
280 } else {
281 (col, row)
282 }
283 })
284 }
285
286 pub fn remove_row(&mut self, sheet: &str, row: u32) -> Result<()> {
288 let sheet_idx = self.sheet_index(sheet)?;
289 {
290 let ws = self.worksheet_mut_by_index(sheet_idx)?;
291 crate::row::remove_row(ws, row)?;
292 }
293 self.apply_reference_shift_for_sheet(sheet_idx, |col, r| {
294 if r > row {
295 (col, r - 1)
296 } else {
297 (col, r)
298 }
299 })
300 }
301
302 pub fn duplicate_row(&mut self, sheet: &str, row: u32) -> Result<()> {
304 let ws = self.worksheet_mut(sheet)?;
305 crate::row::duplicate_row(ws, row)
306 }
307
308 pub fn set_row_height(&mut self, sheet: &str, row: u32, height: f64) -> Result<()> {
310 let ws = self.worksheet_mut(sheet)?;
311 crate::row::set_row_height(ws, row, height)
312 }
313
314 pub fn get_row_height(&self, sheet: &str, row: u32) -> Result<Option<f64>> {
316 let ws = self.worksheet_ref(sheet)?;
317 Ok(crate::row::get_row_height(ws, row))
318 }
319
320 pub fn set_row_visible(&mut self, sheet: &str, row: u32, visible: bool) -> Result<()> {
322 let ws = self.worksheet_mut(sheet)?;
323 crate::row::set_row_visible(ws, row, visible)
324 }
325
326 pub fn get_row_visible(&self, sheet: &str, row: u32) -> Result<bool> {
328 let ws = self.worksheet_ref(sheet)?;
329 Ok(crate::row::get_row_visible(ws, row))
330 }
331
332 pub fn set_row_outline_level(&mut self, sheet: &str, row: u32, level: u8) -> Result<()> {
334 let ws = self.worksheet_mut(sheet)?;
335 crate::row::set_row_outline_level(ws, row, level)
336 }
337
338 pub fn get_row_outline_level(&self, sheet: &str, row: u32) -> Result<u8> {
340 let ws = self.worksheet_ref(sheet)?;
341 Ok(crate::row::get_row_outline_level(ws, row))
342 }
343
344 pub fn set_row_style(&mut self, sheet: &str, row: u32, style_id: u32) -> Result<()> {
348 if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
349 return Err(Error::StyleNotFound { id: style_id });
350 }
351 let ws = self.worksheet_mut(sheet)?;
352 crate::row::set_row_style(ws, row, style_id)
353 }
354
355 pub fn get_row_style(&self, sheet: &str, row: u32) -> Result<u32> {
357 let ws = self.worksheet_ref(sheet)?;
358 Ok(crate::row::get_row_style(ws, row))
359 }
360
361 #[allow(clippy::type_complexity)]
367 pub fn get_rows(&self, sheet: &str) -> Result<Vec<(u32, Vec<(u32, CellValue)>)>> {
368 let ws = self.worksheet_ref(sheet)?;
369 let style_is_date = self.computed_style_is_date();
370 crate::row::get_rows(ws, &self.sst_runtime, &style_is_date)
371 }
372
373 #[allow(clippy::type_complexity)]
378 pub fn get_cols(&self, sheet: &str) -> Result<Vec<(String, Vec<(u32, CellValue)>)>> {
379 let ws = self.worksheet_ref(sheet)?;
380 let style_is_date = self.computed_style_is_date();
381 crate::col::get_cols(ws, &self.sst_runtime, &style_is_date)
382 }
383
384 pub fn set_col_width(&mut self, sheet: &str, col: &str, width: f64) -> Result<()> {
386 let ws = self.worksheet_mut(sheet)?;
387 crate::col::set_col_width(ws, col, width)
388 }
389
390 pub fn get_col_width(&self, sheet: &str, col: &str) -> Result<Option<f64>> {
392 let ws = self.worksheet_ref(sheet)?;
393 Ok(crate::col::get_col_width(ws, col))
394 }
395
396 pub fn set_col_visible(&mut self, sheet: &str, col: &str, visible: bool) -> Result<()> {
398 let ws = self.worksheet_mut(sheet)?;
399 crate::col::set_col_visible(ws, col, visible)
400 }
401
402 pub fn get_col_visible(&self, sheet: &str, col: &str) -> Result<bool> {
404 let ws = self.worksheet_ref(sheet)?;
405 crate::col::get_col_visible(ws, col)
406 }
407
408 pub fn set_col_outline_level(&mut self, sheet: &str, col: &str, level: u8) -> Result<()> {
410 let ws = self.worksheet_mut(sheet)?;
411 crate::col::set_col_outline_level(ws, col, level)
412 }
413
414 pub fn get_col_outline_level(&self, sheet: &str, col: &str) -> Result<u8> {
416 let ws = self.worksheet_ref(sheet)?;
417 crate::col::get_col_outline_level(ws, col)
418 }
419
420 pub fn set_col_style(&mut self, sheet: &str, col: &str, style_id: u32) -> Result<()> {
424 if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
425 return Err(Error::StyleNotFound { id: style_id });
426 }
427 let ws = self.worksheet_mut(sheet)?;
428 crate::col::set_col_style(ws, col, style_id)
429 }
430
431 pub fn get_col_style(&self, sheet: &str, col: &str) -> Result<u32> {
433 let ws = self.worksheet_ref(sheet)?;
434 crate::col::get_col_style(ws, col)
435 }
436
437 pub fn insert_cols(&mut self, sheet: &str, col: &str, count: u32) -> Result<()> {
439 let sheet_idx = self.sheet_index(sheet)?;
440 let start_col = column_name_to_number(col)?;
441 {
442 let ws = self.worksheet_mut_by_index(sheet_idx)?;
443 crate::col::insert_cols(ws, col, count)?;
444 }
445 self.apply_reference_shift_for_sheet(sheet_idx, |c, row| {
446 if c >= start_col {
447 (c + count, row)
448 } else {
449 (c, row)
450 }
451 })
452 }
453
454 pub fn remove_col(&mut self, sheet: &str, col: &str) -> Result<()> {
456 let sheet_idx = self.sheet_index(sheet)?;
457 let col_num = column_name_to_number(col)?;
458 {
459 let ws = self.worksheet_mut_by_index(sheet_idx)?;
460 crate::col::remove_col(ws, col)?;
461 }
462 self.apply_reference_shift_for_sheet(sheet_idx, |c, row| {
463 if c > col_num {
464 (c - 1, row)
465 } else {
466 (c, row)
467 }
468 })
469 }
470
471 pub(crate) fn reindex_sheet_maps_after_delete(&mut self, removed_idx: usize) {
473 self.worksheet_rels = self
474 .worksheet_rels
475 .iter()
476 .filter_map(|(idx, rels)| {
477 if *idx == removed_idx {
478 None
479 } else if *idx > removed_idx {
480 Some((idx - 1, rels.clone()))
481 } else {
482 Some((*idx, rels.clone()))
483 }
484 })
485 .collect();
486
487 self.worksheet_drawings = self
488 .worksheet_drawings
489 .iter()
490 .filter_map(|(idx, drawing_idx)| {
491 if *idx == removed_idx {
492 None
493 } else if *idx > removed_idx {
494 Some((idx - 1, *drawing_idx))
495 } else {
496 Some((*idx, *drawing_idx))
497 }
498 })
499 .collect();
500 }
501
502 pub(crate) fn apply_reference_shift_for_sheet<F>(
504 &mut self,
505 sheet_idx: usize,
506 shift_cell: F,
507 ) -> Result<()>
508 where
509 F: Fn(u32, u32) -> (u32, u32) + Copy,
510 {
511 {
512 let ws = self.worksheet_mut_by_index(sheet_idx)?;
513
514 for row in &mut ws.sheet_data.rows {
516 for cell in &mut row.cells {
517 if let Some(ref mut f) = cell.f {
518 if let Some(ref mut expr) = f.value {
519 *expr = shift_cell_references_in_text(expr, shift_cell)?;
520 }
521 }
522 }
523 }
524
525 if let Some(ref mut merges) = ws.merge_cells {
527 for mc in &mut merges.merge_cells {
528 mc.reference = shift_cell_references_in_text(&mc.reference, shift_cell)?;
529 }
530 merges.cached_coords.clear();
532 }
533
534 if let Some(ref mut af) = ws.auto_filter {
536 af.reference = shift_cell_references_in_text(&af.reference, shift_cell)?;
537 }
538
539 if let Some(ref mut dvs) = ws.data_validations {
541 for dv in &mut dvs.data_validations {
542 dv.sqref = shift_cell_references_in_text(&dv.sqref, shift_cell)?;
543 if let Some(ref mut f1) = dv.formula1 {
544 *f1 = shift_cell_references_in_text(f1, shift_cell)?;
545 }
546 if let Some(ref mut f2) = dv.formula2 {
547 *f2 = shift_cell_references_in_text(f2, shift_cell)?;
548 }
549 }
550 }
551
552 for cf in &mut ws.conditional_formatting {
554 cf.sqref = shift_cell_references_in_text(&cf.sqref, shift_cell)?;
555 for rule in &mut cf.cf_rules {
556 for f in &mut rule.formulas {
557 *f = shift_cell_references_in_text(f, shift_cell)?;
558 }
559 }
560 }
561
562 if let Some(ref mut hyperlinks) = ws.hyperlinks {
564 for hl in &mut hyperlinks.hyperlinks {
565 hl.reference = shift_cell_references_in_text(&hl.reference, shift_cell)?;
566 if let Some(ref mut loc) = hl.location {
567 *loc = shift_cell_references_in_text(loc, shift_cell)?;
568 }
569 }
570 }
571
572 if let Some(ref mut views) = ws.sheet_views {
574 for view in &mut views.sheet_views {
575 if let Some(ref mut pane) = view.pane {
576 if let Some(ref mut top_left) = pane.top_left_cell {
577 *top_left = shift_cell_references_in_text(top_left, shift_cell)?;
578 }
579 }
580 for sel in &mut view.selection {
581 if let Some(ref mut ac) = sel.active_cell {
582 *ac = shift_cell_references_in_text(ac, shift_cell)?;
583 }
584 if let Some(ref mut sqref) = sel.sqref {
585 *sqref = shift_cell_references_in_text(sqref, shift_cell)?;
586 }
587 }
588 }
589 }
590 }
591
592 if let Some(&drawing_idx) = self.worksheet_drawings.get(&sheet_idx) {
594 if let Some((_, drawing)) = self.drawings.get_mut(drawing_idx) {
595 for anchor in &mut drawing.one_cell_anchors {
596 let (new_col, new_row) = shift_cell(anchor.from.col + 1, anchor.from.row + 1);
597 anchor.from.col = new_col - 1;
598 anchor.from.row = new_row - 1;
599 }
600 for anchor in &mut drawing.two_cell_anchors {
601 let (from_col, from_row) = shift_cell(anchor.from.col + 1, anchor.from.row + 1);
602 anchor.from.col = from_col - 1;
603 anchor.from.row = from_row - 1;
604 let (to_col, to_row) = shift_cell(anchor.to.col + 1, anchor.to.row + 1);
605 anchor.to.col = to_col - 1;
606 anchor.to.row = to_row - 1;
607 }
608 }
609 }
610
611 Ok(())
612 }
613
614 pub(crate) fn ensure_drawing_for_sheet(&mut self, sheet_idx: usize) -> usize {
617 if let Some(&idx) = self.worksheet_drawings.get(&sheet_idx) {
618 return idx;
619 }
620
621 let idx = self.drawings.len();
622 let drawing_path = format!("xl/drawings/drawing{}.xml", idx + 1);
623 self.drawings.push((drawing_path, WsDr::default()));
624 self.worksheet_drawings.insert(sheet_idx, idx);
625
626 let ws_rid = self.next_worksheet_rid(sheet_idx);
628 self.ensure_hydrated(sheet_idx)
632 .expect("sheet must be hydrated before attaching a drawing");
633 self.mark_sheet_dirty(sheet_idx);
634 self.worksheets[sheet_idx].1.get_mut().unwrap().drawing = Some(DrawingRef {
635 r_id: ws_rid.clone(),
636 });
637
638 let drawing_rel_target = format!("../drawings/drawing{}.xml", idx + 1);
640 let ws_rels = self
641 .worksheet_rels
642 .entry(sheet_idx)
643 .or_insert_with(|| Relationships {
644 xmlns: sheetkit_xml::namespaces::PACKAGE_RELATIONSHIPS.to_string(),
645 relationships: vec![],
646 });
647 ws_rels.relationships.push(Relationship {
648 id: ws_rid,
649 rel_type: rel_types::DRAWING.to_string(),
650 target: drawing_rel_target,
651 target_mode: None,
652 });
653
654 self.content_types.overrides.push(ContentTypeOverride {
656 part_name: format!("/xl/drawings/drawing{}.xml", idx + 1),
657 content_type: mime_types::DRAWING.to_string(),
658 });
659
660 idx
661 }
662
663 pub(crate) fn next_worksheet_rid(&self, sheet_idx: usize) -> String {
665 let existing = self
666 .worksheet_rels
667 .get(&sheet_idx)
668 .map(|r| r.relationships.as_slice())
669 .unwrap_or(&[]);
670 crate::sheet::next_rid(existing)
671 }
672
673 pub(crate) fn next_drawing_rid(&self, drawing_idx: usize) -> String {
675 let existing = self
676 .drawing_rels
677 .get(&drawing_idx)
678 .map(|r| r.relationships.as_slice())
679 .unwrap_or(&[]);
680 crate::sheet::next_rid(existing)
681 }
682}
683
684#[cfg(test)]
685#[allow(clippy::approx_constant)]
686mod tests {
687 use super::*;
688 use tempfile::TempDir;
689
690 #[test]
691 fn test_new_sheet_basic() {
692 let mut wb = Workbook::new();
693 let idx = wb.new_sheet("Sheet2").unwrap();
694 assert_eq!(idx, 1);
695 assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet2"]);
696 }
697
698 #[test]
699 fn test_new_sheet_duplicate_returns_error() {
700 let mut wb = Workbook::new();
701 let result = wb.new_sheet("Sheet1");
702 assert!(result.is_err());
703 assert!(matches!(
704 result.unwrap_err(),
705 Error::SheetAlreadyExists { .. }
706 ));
707 }
708
709 #[test]
710 fn test_new_sheet_invalid_name_returns_error() {
711 let mut wb = Workbook::new();
712 let result = wb.new_sheet("Bad/Name");
713 assert!(result.is_err());
714 assert!(matches!(result.unwrap_err(), Error::InvalidSheetName(_)));
715 }
716
717 #[test]
718 fn test_delete_sheet_basic() {
719 let mut wb = Workbook::new();
720 wb.new_sheet("Sheet2").unwrap();
721 wb.delete_sheet("Sheet1").unwrap();
722 assert_eq!(wb.sheet_names(), vec!["Sheet2"]);
723 }
724
725 #[test]
726 fn test_delete_sheet_keeps_parallel_vecs_in_sync() {
727 let mut wb = Workbook::new();
728 wb.new_sheet("Sheet2").unwrap();
729 wb.new_sheet("Sheet3").unwrap();
730
731 wb.add_comment(
733 "Sheet2",
734 &crate::comment::CommentConfig {
735 cell: "A1".to_string(),
736 author: "Test".to_string(),
737 text: "note".to_string(),
738 },
739 )
740 .unwrap();
741
742 wb.delete_sheet("Sheet2").unwrap();
744 assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet3"]);
745
746 wb.add_comment(
749 "Sheet3",
750 &crate::comment::CommentConfig {
751 cell: "B2".to_string(),
752 author: "Test".to_string(),
753 text: "note2".to_string(),
754 },
755 )
756 .unwrap();
757 }
758
759 #[test]
760 fn test_delete_last_sheet_returns_error() {
761 let mut wb = Workbook::new();
762 let result = wb.delete_sheet("Sheet1");
763 assert!(result.is_err());
764 }
765
766 #[test]
767 fn test_delete_nonexistent_sheet_returns_error() {
768 let mut wb = Workbook::new();
769 let result = wb.delete_sheet("NoSuchSheet");
770 assert!(result.is_err());
771 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
772 }
773
774 #[test]
775 fn test_set_sheet_name_basic() {
776 let mut wb = Workbook::new();
777 wb.set_sheet_name("Sheet1", "Renamed").unwrap();
778 assert_eq!(wb.sheet_names(), vec!["Renamed"]);
779 }
780
781 #[test]
782 fn test_set_sheet_name_to_existing_returns_error() {
783 let mut wb = Workbook::new();
784 wb.new_sheet("Sheet2").unwrap();
785 let result = wb.set_sheet_name("Sheet1", "Sheet2");
786 assert!(result.is_err());
787 assert!(matches!(
788 result.unwrap_err(),
789 Error::SheetAlreadyExists { .. }
790 ));
791 }
792
793 #[test]
794 fn test_copy_sheet_basic() {
795 let mut wb = Workbook::new();
796 let idx = wb.copy_sheet("Sheet1", "Sheet1 Copy").unwrap();
797 assert_eq!(idx, 1);
798 assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet1 Copy"]);
799 }
800
801 #[test]
802 fn test_get_sheet_index() {
803 let mut wb = Workbook::new();
804 wb.new_sheet("Sheet2").unwrap();
805 assert_eq!(wb.get_sheet_index("Sheet1"), Some(0));
806 assert_eq!(wb.get_sheet_index("Sheet2"), Some(1));
807 assert_eq!(wb.get_sheet_index("Nonexistent"), None);
808 }
809
810 #[test]
811 fn test_get_active_sheet_default() {
812 let wb = Workbook::new();
813 assert_eq!(wb.get_active_sheet(), "Sheet1");
814 }
815
816 #[test]
817 fn test_set_active_sheet() {
818 let mut wb = Workbook::new();
819 wb.new_sheet("Sheet2").unwrap();
820 wb.set_active_sheet("Sheet2").unwrap();
821 assert_eq!(wb.get_active_sheet(), "Sheet2");
822 }
823
824 #[test]
825 fn test_set_active_sheet_not_found() {
826 let mut wb = Workbook::new();
827 let result = wb.set_active_sheet("NoSuchSheet");
828 assert!(result.is_err());
829 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
830 }
831
832 #[test]
833 fn test_sheet_management_roundtrip_save_open() {
834 let dir = TempDir::new().unwrap();
835 let path = dir.path().join("sheet_mgmt.xlsx");
836
837 let mut wb = Workbook::new();
838 wb.new_sheet("Data").unwrap();
839 wb.new_sheet("Summary").unwrap();
840 wb.set_sheet_name("Sheet1", "Overview").unwrap();
841 wb.save(&path).unwrap();
842
843 let wb2 = Workbook::open(&path).unwrap();
844 assert_eq!(wb2.sheet_names(), vec!["Overview", "Data", "Summary"]);
845 }
846
847 #[test]
848 fn test_workbook_insert_rows() {
849 let mut wb = Workbook::new();
850 wb.set_cell_value("Sheet1", "A1", "stay").unwrap();
851 wb.set_cell_value("Sheet1", "A2", "shift").unwrap();
852 wb.insert_rows("Sheet1", 2, 1).unwrap();
853
854 assert_eq!(
855 wb.get_cell_value("Sheet1", "A1").unwrap(),
856 CellValue::String("stay".to_string())
857 );
858 assert_eq!(
859 wb.get_cell_value("Sheet1", "A3").unwrap(),
860 CellValue::String("shift".to_string())
861 );
862 assert_eq!(wb.get_cell_value("Sheet1", "A2").unwrap(), CellValue::Empty);
863 }
864
865 #[test]
866 fn test_workbook_insert_rows_updates_formula_and_ranges() {
867 let mut wb = Workbook::new();
868 wb.set_cell_value(
869 "Sheet1",
870 "C1",
871 CellValue::Formula {
872 expr: "SUM(A2:B2)".to_string(),
873 result: None,
874 },
875 )
876 .unwrap();
877 wb.add_data_validation(
878 "Sheet1",
879 &crate::validation::DataValidationConfig::whole_number("A2:A5", 1, 9),
880 )
881 .unwrap();
882 wb.set_auto_filter("Sheet1", "A2:B10").unwrap();
883 wb.merge_cells("Sheet1", "A2", "B3").unwrap();
884
885 wb.insert_rows("Sheet1", 2, 1).unwrap();
886
887 match wb.get_cell_value("Sheet1", "C1").unwrap() {
888 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A3:B3)"),
889 other => panic!("expected formula, got {other:?}"),
890 }
891
892 let validations = wb.get_data_validations("Sheet1").unwrap();
893 assert_eq!(validations.len(), 1);
894 assert_eq!(validations[0].sqref, "A3:A6");
895
896 let merges = wb.get_merge_cells("Sheet1").unwrap();
897 assert_eq!(merges, vec!["A3:B4".to_string()]);
898
899 let ws = wb.worksheet_ref("Sheet1").unwrap();
900 assert_eq!(ws.auto_filter.as_ref().unwrap().reference, "A3:B11");
901 }
902
903 #[test]
904 fn test_workbook_insert_rows_sheet_not_found() {
905 let mut wb = Workbook::new();
906 let result = wb.insert_rows("NoSheet", 1, 1);
907 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
908 }
909
910 #[test]
911 fn test_workbook_remove_row() {
912 let mut wb = Workbook::new();
913 wb.set_cell_value("Sheet1", "A1", "first").unwrap();
914 wb.set_cell_value("Sheet1", "A2", "second").unwrap();
915 wb.set_cell_value("Sheet1", "A3", "third").unwrap();
916 wb.remove_row("Sheet1", 2).unwrap();
917
918 assert_eq!(
919 wb.get_cell_value("Sheet1", "A1").unwrap(),
920 CellValue::String("first".to_string())
921 );
922 assert_eq!(
923 wb.get_cell_value("Sheet1", "A2").unwrap(),
924 CellValue::String("third".to_string())
925 );
926 }
927
928 #[test]
929 fn test_workbook_remove_row_sheet_not_found() {
930 let mut wb = Workbook::new();
931 let result = wb.remove_row("NoSheet", 1);
932 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
933 }
934
935 #[test]
936 fn test_workbook_duplicate_row() {
937 let mut wb = Workbook::new();
938 wb.set_cell_value("Sheet1", "A1", "original").unwrap();
939 wb.duplicate_row("Sheet1", 1).unwrap();
940
941 assert_eq!(
942 wb.get_cell_value("Sheet1", "A1").unwrap(),
943 CellValue::String("original".to_string())
944 );
945 assert_eq!(
947 wb.get_cell_value("Sheet1", "A2").unwrap(),
948 CellValue::String("original".to_string())
949 );
950 }
951
952 #[test]
953 fn test_workbook_set_and_get_row_height() {
954 let mut wb = Workbook::new();
955 wb.set_row_height("Sheet1", 3, 25.0).unwrap();
956 assert_eq!(wb.get_row_height("Sheet1", 3).unwrap(), Some(25.0));
957 }
958
959 #[test]
960 fn test_workbook_get_row_height_sheet_not_found() {
961 let wb = Workbook::new();
962 let result = wb.get_row_height("NoSheet", 1);
963 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
964 }
965
966 #[test]
967 fn test_workbook_set_row_visible() {
968 let mut wb = Workbook::new();
969 wb.set_row_visible("Sheet1", 1, false).unwrap();
970 }
971
972 #[test]
973 fn test_workbook_set_row_visible_sheet_not_found() {
974 let mut wb = Workbook::new();
975 let result = wb.set_row_visible("NoSheet", 1, false);
976 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
977 }
978
979 #[test]
980 fn test_workbook_set_and_get_col_width() {
981 let mut wb = Workbook::new();
982 wb.set_col_width("Sheet1", "A", 18.0).unwrap();
983 assert_eq!(wb.get_col_width("Sheet1", "A").unwrap(), Some(18.0));
984 }
985
986 #[test]
987 fn test_workbook_get_col_width_sheet_not_found() {
988 let wb = Workbook::new();
989 let result = wb.get_col_width("NoSheet", "A");
990 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
991 }
992
993 #[test]
994 fn test_workbook_set_col_visible() {
995 let mut wb = Workbook::new();
996 wb.set_col_visible("Sheet1", "B", false).unwrap();
997 }
998
999 #[test]
1000 fn test_workbook_set_col_visible_sheet_not_found() {
1001 let mut wb = Workbook::new();
1002 let result = wb.set_col_visible("NoSheet", "A", false);
1003 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1004 }
1005
1006 #[test]
1007 fn test_workbook_insert_cols() {
1008 let mut wb = Workbook::new();
1009 wb.set_cell_value("Sheet1", "A1", "a").unwrap();
1010 wb.set_cell_value("Sheet1", "B1", "b").unwrap();
1011 wb.insert_cols("Sheet1", "B", 1).unwrap();
1012
1013 assert_eq!(
1014 wb.get_cell_value("Sheet1", "A1").unwrap(),
1015 CellValue::String("a".to_string())
1016 );
1017 assert_eq!(wb.get_cell_value("Sheet1", "B1").unwrap(), CellValue::Empty);
1018 assert_eq!(
1019 wb.get_cell_value("Sheet1", "C1").unwrap(),
1020 CellValue::String("b".to_string())
1021 );
1022 }
1023
1024 #[test]
1025 fn test_workbook_insert_cols_updates_formula_and_ranges() {
1026 let mut wb = Workbook::new();
1027 wb.set_cell_value(
1028 "Sheet1",
1029 "D1",
1030 CellValue::Formula {
1031 expr: "SUM(A1:B1)".to_string(),
1032 result: None,
1033 },
1034 )
1035 .unwrap();
1036 wb.add_data_validation(
1037 "Sheet1",
1038 &crate::validation::DataValidationConfig::whole_number("B2:C3", 1, 9),
1039 )
1040 .unwrap();
1041 wb.set_auto_filter("Sheet1", "A1:C10").unwrap();
1042 wb.merge_cells("Sheet1", "B3", "C4").unwrap();
1043
1044 wb.insert_cols("Sheet1", "B", 2).unwrap();
1045
1046 match wb.get_cell_value("Sheet1", "F1").unwrap() {
1047 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A1:D1)"),
1048 other => panic!("expected formula, got {other:?}"),
1049 }
1050
1051 let validations = wb.get_data_validations("Sheet1").unwrap();
1052 assert_eq!(validations.len(), 1);
1053 assert_eq!(validations[0].sqref, "D2:E3");
1054
1055 let merges = wb.get_merge_cells("Sheet1").unwrap();
1056 assert_eq!(merges, vec!["D3:E4".to_string()]);
1057
1058 let ws = wb.worksheet_ref("Sheet1").unwrap();
1059 assert_eq!(ws.auto_filter.as_ref().unwrap().reference, "A1:E10");
1060 }
1061
1062 #[test]
1063 fn test_workbook_insert_cols_sheet_not_found() {
1064 let mut wb = Workbook::new();
1065 let result = wb.insert_cols("NoSheet", "A", 1);
1066 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1067 }
1068
1069 #[test]
1070 fn test_workbook_remove_col() {
1071 let mut wb = Workbook::new();
1072 wb.set_cell_value("Sheet1", "A1", "a").unwrap();
1073 wb.set_cell_value("Sheet1", "B1", "b").unwrap();
1074 wb.set_cell_value("Sheet1", "C1", "c").unwrap();
1075 wb.remove_col("Sheet1", "B").unwrap();
1076
1077 assert_eq!(
1078 wb.get_cell_value("Sheet1", "A1").unwrap(),
1079 CellValue::String("a".to_string())
1080 );
1081 assert_eq!(
1082 wb.get_cell_value("Sheet1", "B1").unwrap(),
1083 CellValue::String("c".to_string())
1084 );
1085 }
1086
1087 #[test]
1088 fn test_workbook_remove_col_sheet_not_found() {
1089 let mut wb = Workbook::new();
1090 let result = wb.remove_col("NoSheet", "A");
1091 assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
1092 }
1093
1094 #[test]
1095 fn test_new_stream_writer_validates_name() {
1096 let wb = Workbook::new();
1097 let result = wb.new_stream_writer("Bad[Name");
1098 assert!(result.is_err());
1099 assert!(matches!(result.unwrap_err(), Error::InvalidSheetName(_)));
1100 }
1101
1102 #[test]
1103 fn test_new_stream_writer_rejects_duplicate() {
1104 let wb = Workbook::new();
1105 let result = wb.new_stream_writer("Sheet1");
1106 assert!(result.is_err());
1107 assert!(matches!(
1108 result.unwrap_err(),
1109 Error::SheetAlreadyExists { .. }
1110 ));
1111 }
1112
1113 #[test]
1114 fn test_new_stream_writer_valid_name() {
1115 let wb = Workbook::new();
1116 let sw = wb.new_stream_writer("StreamSheet").unwrap();
1117 assert_eq!(sw.sheet_name(), "StreamSheet");
1118 }
1119
1120 #[test]
1121 fn test_apply_stream_writer_adds_sheet() {
1122 let mut wb = Workbook::new();
1123 let mut sw = wb.new_stream_writer("StreamSheet").unwrap();
1124 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1125 .unwrap();
1126 let idx = wb.apply_stream_writer(sw).unwrap();
1127 assert_eq!(idx, 1);
1128 assert_eq!(wb.sheet_names(), vec!["Sheet1", "StreamSheet"]);
1129 }
1130
1131 #[test]
1132 fn test_apply_stream_writer_uses_inline_strings() {
1133 let mut wb = Workbook::new();
1135 wb.set_cell_value("Sheet1", "A1", "Existing").unwrap();
1136 let sst_before = wb.sst_runtime.len();
1137
1138 let mut sw = wb.new_stream_writer("StreamSheet").unwrap();
1139 sw.write_row(1, &[CellValue::from("New"), CellValue::from("Existing")])
1140 .unwrap();
1141 wb.apply_stream_writer(sw).unwrap();
1142
1143 assert_eq!(wb.sst_runtime.len(), sst_before);
1145 }
1146
1147 #[test]
1148 fn test_stream_writer_save_and_reopen() {
1149 let dir = TempDir::new().unwrap();
1150 let path = dir.path().join("stream_test.xlsx");
1151
1152 let mut wb = Workbook::new();
1153 wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1154
1155 let mut sw = wb.new_stream_writer("Streamed").unwrap();
1156 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Value")])
1157 .unwrap();
1158 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(100)])
1159 .unwrap();
1160 sw.write_row(3, &[CellValue::from("Bob"), CellValue::from(200)])
1161 .unwrap();
1162 wb.apply_stream_writer(sw).unwrap();
1163
1164 wb.save(&path).unwrap();
1165
1166 let wb2 = Workbook::open(&path).unwrap();
1167 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Streamed"]);
1168 assert_eq!(
1169 wb2.get_cell_value("Sheet1", "A1").unwrap(),
1170 CellValue::String("Normal".to_string())
1171 );
1172 assert_eq!(
1173 wb2.get_cell_value("Streamed", "A1").unwrap(),
1174 CellValue::String("Name".to_string())
1175 );
1176 assert_eq!(
1177 wb2.get_cell_value("Streamed", "B2").unwrap(),
1178 CellValue::Number(100.0)
1179 );
1180 assert_eq!(
1181 wb2.get_cell_value("Streamed", "A3").unwrap(),
1182 CellValue::String("Bob".to_string())
1183 );
1184 }
1185
1186 #[test]
1187 fn test_workbook_get_rows_empty_sheet() {
1188 let wb = Workbook::new();
1189 let rows = wb.get_rows("Sheet1").unwrap();
1190 assert!(rows.is_empty());
1191 }
1192
1193 #[test]
1194 fn test_workbook_get_rows_with_data() {
1195 let mut wb = Workbook::new();
1196 wb.set_cell_value("Sheet1", "A1", "Name").unwrap();
1197 wb.set_cell_value("Sheet1", "B1", 42.0).unwrap();
1198 wb.set_cell_value("Sheet1", "A2", "Alice").unwrap();
1199 wb.set_cell_value("Sheet1", "B2", true).unwrap();
1200
1201 let rows = wb.get_rows("Sheet1").unwrap();
1202 assert_eq!(rows.len(), 2);
1203 assert_eq!(rows[0].0, 1);
1204 assert_eq!(rows[0].1.len(), 2);
1205 assert_eq!(rows[0].1[0].0, 1);
1206 assert_eq!(rows[0].1[0].1, CellValue::String("Name".to_string()));
1207 assert_eq!(rows[0].1[1].0, 2);
1208 assert_eq!(rows[0].1[1].1, CellValue::Number(42.0));
1209 assert_eq!(rows[1].0, 2);
1210 assert_eq!(rows[1].1[0].1, CellValue::String("Alice".to_string()));
1211 assert_eq!(rows[1].1[1].1, CellValue::Bool(true));
1212 }
1213
1214 #[test]
1215 fn test_workbook_get_rows_sheet_not_found() {
1216 let wb = Workbook::new();
1217 assert!(wb.get_rows("NoSheet").is_err());
1218 }
1219
1220 #[test]
1221 fn test_workbook_get_cols_empty_sheet() {
1222 let wb = Workbook::new();
1223 let cols = wb.get_cols("Sheet1").unwrap();
1224 assert!(cols.is_empty());
1225 }
1226
1227 #[test]
1228 fn test_workbook_get_cols_with_data() {
1229 let mut wb = Workbook::new();
1230 wb.set_cell_value("Sheet1", "A1", "Name").unwrap();
1231 wb.set_cell_value("Sheet1", "B1", 42.0).unwrap();
1232 wb.set_cell_value("Sheet1", "A2", "Alice").unwrap();
1233 wb.set_cell_value("Sheet1", "B2", 30.0).unwrap();
1234
1235 let cols = wb.get_cols("Sheet1").unwrap();
1236 assert_eq!(cols.len(), 2);
1237 assert_eq!(cols[0].0, "A");
1238 assert_eq!(cols[0].1.len(), 2);
1239 assert_eq!(cols[0].1[0], (1, CellValue::String("Name".to_string())));
1240 assert_eq!(cols[0].1[1], (2, CellValue::String("Alice".to_string())));
1241 assert_eq!(cols[1].0, "B");
1242 assert_eq!(cols[1].1[0], (1, CellValue::Number(42.0)));
1243 assert_eq!(cols[1].1[1], (2, CellValue::Number(30.0)));
1244 }
1245
1246 #[test]
1247 fn test_workbook_get_cols_sheet_not_found() {
1248 let wb = Workbook::new();
1249 assert!(wb.get_cols("NoSheet").is_err());
1250 }
1251
1252 #[test]
1253 fn test_streamed_sheet_cells_empty_before_save() {
1254 let mut wb = Workbook::new();
1257 let mut sw = wb.new_stream_writer("Streamed").unwrap();
1258 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Age")])
1259 .unwrap();
1260 wb.apply_stream_writer(sw).unwrap();
1261
1262 assert_eq!(
1263 wb.get_cell_value("Streamed", "A1").unwrap(),
1264 CellValue::Empty
1265 );
1266 assert_eq!(
1267 wb.get_cell_value("Streamed", "B1").unwrap(),
1268 CellValue::Empty
1269 );
1270 }
1271
1272 #[test]
1273 fn test_streamed_sheet_readable_after_save_reopen() {
1274 let dir = TempDir::new().unwrap();
1275 let path = dir.path().join("stream_reopen.xlsx");
1276
1277 let mut wb = Workbook::new();
1278 let mut sw = wb.new_stream_writer("Streamed").unwrap();
1279 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Age")])
1280 .unwrap();
1281 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(30)])
1282 .unwrap();
1283 wb.apply_stream_writer(sw).unwrap();
1284 wb.save(&path).unwrap();
1285
1286 let wb2 = Workbook::open(&path).unwrap();
1287 assert_eq!(
1288 wb2.get_cell_value("Streamed", "A1").unwrap(),
1289 CellValue::String("Name".to_string())
1290 );
1291 assert_eq!(
1292 wb2.get_cell_value("Streamed", "B1").unwrap(),
1293 CellValue::String("Age".to_string())
1294 );
1295 assert_eq!(
1296 wb2.get_cell_value("Streamed", "A2").unwrap(),
1297 CellValue::String("Alice".to_string())
1298 );
1299 assert_eq!(
1300 wb2.get_cell_value("Streamed", "B2").unwrap(),
1301 CellValue::Number(30.0)
1302 );
1303 }
1304
1305 #[test]
1306 fn test_workbook_get_rows_roundtrip_save_open() {
1307 let mut wb = Workbook::new();
1308 wb.set_cell_value("Sheet1", "A1", "hello").unwrap();
1309 wb.set_cell_value("Sheet1", "B1", 99.0).unwrap();
1310 wb.set_cell_value("Sheet1", "A2", true).unwrap();
1311
1312 let tmp = std::env::temp_dir().join("test_get_rows_roundtrip.xlsx");
1313 wb.save(&tmp).unwrap();
1314
1315 let wb2 = Workbook::open(&tmp).unwrap();
1316 let rows = wb2.get_rows("Sheet1").unwrap();
1317 assert_eq!(rows.len(), 2);
1318 assert_eq!(rows[0].1[0].1, CellValue::String("hello".to_string()));
1319 assert_eq!(rows[0].1[1].1, CellValue::Number(99.0));
1320 assert_eq!(rows[1].1[0].1, CellValue::Bool(true));
1321
1322 let _ = std::fs::remove_file(&tmp);
1323 }
1324
1325 #[test]
1326 fn test_stream_save_reopen_basic() {
1327 let dir = TempDir::new().unwrap();
1328 let path = dir.path().join("stream_basic.xlsx");
1329
1330 let mut wb = Workbook::new();
1331 let mut sw = wb.new_stream_writer("Optimized").unwrap();
1332 sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1333 .unwrap();
1334 sw.write_row(2, &[CellValue::from("World"), CellValue::from(99)])
1335 .unwrap();
1336 let idx = wb.apply_stream_writer(sw).unwrap();
1337 assert_eq!(idx, 1);
1338
1339 wb.save(&path).unwrap();
1340 let wb2 = Workbook::open(&path).unwrap();
1341 assert_eq!(
1342 wb2.get_cell_value("Optimized", "A1").unwrap(),
1343 CellValue::String("Hello".to_string())
1344 );
1345 assert_eq!(
1346 wb2.get_cell_value("Optimized", "B1").unwrap(),
1347 CellValue::Number(42.0)
1348 );
1349 assert_eq!(
1350 wb2.get_cell_value("Optimized", "A2").unwrap(),
1351 CellValue::String("World".to_string())
1352 );
1353 assert_eq!(
1354 wb2.get_cell_value("Optimized", "B2").unwrap(),
1355 CellValue::Number(99.0)
1356 );
1357 }
1358
1359 #[test]
1360 fn test_stream_save_reopen_all_types() {
1361 let dir = TempDir::new().unwrap();
1362 let path = dir.path().join("stream_types.xlsx");
1363
1364 let mut wb = Workbook::new();
1365 let mut sw = wb.new_stream_writer("Types").unwrap();
1366 sw.write_row(
1367 1,
1368 &[
1369 CellValue::from("text"),
1370 CellValue::from(42),
1371 CellValue::from(3.14),
1372 CellValue::from(true),
1373 CellValue::Formula {
1374 expr: "SUM(B1:C1)".to_string(),
1375 result: None,
1376 },
1377 CellValue::Error("#N/A".to_string()),
1378 CellValue::Empty,
1379 ],
1380 )
1381 .unwrap();
1382 wb.apply_stream_writer(sw).unwrap();
1383
1384 wb.save(&path).unwrap();
1385 let wb2 = Workbook::open(&path).unwrap();
1386 assert_eq!(
1387 wb2.get_cell_value("Types", "A1").unwrap(),
1388 CellValue::String("text".to_string())
1389 );
1390 assert_eq!(
1391 wb2.get_cell_value("Types", "B1").unwrap(),
1392 CellValue::Number(42.0)
1393 );
1394 assert_eq!(
1395 wb2.get_cell_value("Types", "D1").unwrap(),
1396 CellValue::Bool(true)
1397 );
1398 match wb2.get_cell_value("Types", "E1").unwrap() {
1399 CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(B1:C1)"),
1400 other => panic!("expected formula, got {other:?}"),
1401 }
1402 assert_eq!(
1403 wb2.get_cell_value("Types", "F1").unwrap(),
1404 CellValue::Error("#N/A".to_string())
1405 );
1406 assert_eq!(wb2.get_cell_value("Types", "G1").unwrap(), CellValue::Empty);
1407 }
1408
1409 #[test]
1410 fn test_apply_stream_optimized_save_reopen() {
1411 let dir = TempDir::new().unwrap();
1412 let path = dir.path().join("stream_optimized.xlsx");
1413
1414 let mut wb = Workbook::new();
1415 wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1416
1417 let mut sw = wb.new_stream_writer("Fast").unwrap();
1418 sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Value")])
1419 .unwrap();
1420 sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(100)])
1421 .unwrap();
1422 sw.write_row(3, &[CellValue::from("Bob"), CellValue::from(200)])
1423 .unwrap();
1424 wb.apply_stream_writer(sw).unwrap();
1425
1426 wb.save(&path).unwrap();
1427
1428 let wb2 = Workbook::open(&path).unwrap();
1429 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Fast"]);
1430 assert_eq!(
1431 wb2.get_cell_value("Fast", "A1").unwrap(),
1432 CellValue::String("Name".to_string())
1433 );
1434 assert_eq!(
1435 wb2.get_cell_value("Fast", "B2").unwrap(),
1436 CellValue::Number(100.0)
1437 );
1438 assert_eq!(
1439 wb2.get_cell_value("Fast", "A3").unwrap(),
1440 CellValue::String("Bob".to_string())
1441 );
1442 }
1443
1444 #[test]
1445 fn test_stream_freeze_panes_roundtrip() {
1446 let dir = TempDir::new().unwrap();
1447 let path = dir.path().join("stream_freeze.xlsx");
1448
1449 let mut wb = Workbook::new();
1450 let mut sw = wb.new_stream_writer("FreezeSheet").unwrap();
1451 sw.set_freeze_panes("B3").unwrap();
1452 sw.write_row(1, &[CellValue::from("A"), CellValue::from("B")])
1453 .unwrap();
1454 sw.write_row(2, &[CellValue::from("C"), CellValue::from("D")])
1455 .unwrap();
1456 wb.apply_stream_writer(sw).unwrap();
1457 wb.save(&path).unwrap();
1458
1459 let wb2 = Workbook::open(&path).unwrap();
1460 assert_eq!(
1461 wb2.get_panes("FreezeSheet").unwrap(),
1462 Some("B3".to_string())
1463 );
1464 assert_eq!(
1465 wb2.get_cell_value("FreezeSheet", "A1").unwrap(),
1466 CellValue::String("A".to_string())
1467 );
1468 }
1469
1470 #[test]
1471 fn test_stream_merge_cells_roundtrip() {
1472 let dir = TempDir::new().unwrap();
1473 let path = dir.path().join("stream_merge.xlsx");
1474
1475 let mut wb = Workbook::new();
1476 let mut sw = wb.new_stream_writer("MergeSheet").unwrap();
1477 sw.add_merge_cell("A1:C1").unwrap();
1478 sw.add_merge_cell("A3:B4").unwrap();
1479 sw.write_row(1, &[CellValue::from("Header")]).unwrap();
1480 sw.write_row(2, &[CellValue::from("Data")]).unwrap();
1481 wb.apply_stream_writer(sw).unwrap();
1482 wb.save(&path).unwrap();
1483
1484 let wb2 = Workbook::open(&path).unwrap();
1485 let merges = wb2.get_merge_cells("MergeSheet").unwrap();
1486 assert!(merges.contains(&"A1:C1".to_string()));
1487 assert!(merges.contains(&"A3:B4".to_string()));
1488 assert_eq!(
1489 wb2.get_cell_value("MergeSheet", "A1").unwrap(),
1490 CellValue::String("Header".to_string())
1491 );
1492 }
1493
1494 #[test]
1495 fn test_stream_col_widths_roundtrip() {
1496 let dir = TempDir::new().unwrap();
1497 let path = dir.path().join("stream_colw.xlsx");
1498
1499 let mut wb = Workbook::new();
1500 let mut sw = wb.new_stream_writer("ColSheet").unwrap();
1501 sw.set_col_width(1, 25.0).unwrap();
1502 sw.set_col_width(2, 12.5).unwrap();
1503 sw.write_row(1, &[CellValue::from("Wide"), CellValue::from("Narrow")])
1504 .unwrap();
1505 wb.apply_stream_writer(sw).unwrap();
1506 wb.save(&path).unwrap();
1507
1508 let wb2 = Workbook::open(&path).unwrap();
1509 let w1 = wb2.get_col_width("ColSheet", "A").unwrap().unwrap();
1510 let w2 = wb2.get_col_width("ColSheet", "B").unwrap().unwrap();
1511 assert!((w1 - 25.0).abs() < 0.01);
1512 assert!((w2 - 12.5).abs() < 0.01);
1513 }
1514
1515 #[test]
1516 fn test_stream_multiple_sheets() {
1517 let dir = TempDir::new().unwrap();
1518 let path = dir.path().join("stream_multi.xlsx");
1519
1520 let mut wb = Workbook::new();
1521 wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1522
1523 let mut sw1 = wb.new_stream_writer("Stream1").unwrap();
1524 sw1.write_row(1, &[CellValue::from("S1R1")]).unwrap();
1525 sw1.write_row(2, &[CellValue::from("S1R2")]).unwrap();
1526 wb.apply_stream_writer(sw1).unwrap();
1527
1528 let mut sw2 = wb.new_stream_writer("Stream2").unwrap();
1529 sw2.write_row(1, &[CellValue::from("S2R1")]).unwrap();
1530 wb.apply_stream_writer(sw2).unwrap();
1531
1532 wb.save(&path).unwrap();
1533
1534 let wb2 = Workbook::open(&path).unwrap();
1535 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Stream1", "Stream2"]);
1536 assert_eq!(
1537 wb2.get_cell_value("Sheet1", "A1").unwrap(),
1538 CellValue::String("Normal".to_string())
1539 );
1540 assert_eq!(
1541 wb2.get_cell_value("Stream1", "A1").unwrap(),
1542 CellValue::String("S1R1".to_string())
1543 );
1544 assert_eq!(
1545 wb2.get_cell_value("Stream1", "A2").unwrap(),
1546 CellValue::String("S1R2".to_string())
1547 );
1548 assert_eq!(
1549 wb2.get_cell_value("Stream2", "A1").unwrap(),
1550 CellValue::String("S2R1".to_string())
1551 );
1552 }
1553
1554 #[test]
1555 fn test_stream_delete_sheet() {
1556 let dir = TempDir::new().unwrap();
1557 let path = dir.path().join("stream_delete.xlsx");
1558
1559 let mut wb = Workbook::new();
1560 let mut sw = wb.new_stream_writer("ToDelete").unwrap();
1561 sw.write_row(1, &[CellValue::from("Gone")]).unwrap();
1562 wb.apply_stream_writer(sw).unwrap();
1563
1564 let mut sw2 = wb.new_stream_writer("Kept").unwrap();
1565 sw2.write_row(1, &[CellValue::from("Stays")]).unwrap();
1566 wb.apply_stream_writer(sw2).unwrap();
1567
1568 wb.delete_sheet("ToDelete").unwrap();
1569 wb.save(&path).unwrap();
1570
1571 let wb2 = Workbook::open(&path).unwrap();
1572 assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Kept"]);
1573 assert_eq!(
1574 wb2.get_cell_value("Kept", "A1").unwrap(),
1575 CellValue::String("Stays".to_string())
1576 );
1577 }
1578
1579 #[test]
1580 fn test_stream_combined_features_roundtrip() {
1581 let dir = TempDir::new().unwrap();
1582 let path = dir.path().join("stream_combined.xlsx");
1583
1584 let mut wb = Workbook::new();
1585 let mut sw = wb.new_stream_writer("Combined").unwrap();
1586 sw.set_freeze_panes("A2").unwrap();
1587 sw.set_col_width(1, 30.0).unwrap();
1588 sw.set_col_width_range(2, 3, 15.0).unwrap();
1589 sw.add_merge_cell("B1:C1").unwrap();
1590 sw.write_row(
1591 1,
1592 &[
1593 CellValue::from("Name"),
1594 CellValue::from("Merged Header"),
1595 CellValue::Empty,
1596 ],
1597 )
1598 .unwrap();
1599 sw.write_row(
1600 2,
1601 &[
1602 CellValue::from("Alice"),
1603 CellValue::from(100),
1604 CellValue::from(true),
1605 ],
1606 )
1607 .unwrap();
1608 wb.apply_stream_writer(sw).unwrap();
1609 wb.save(&path).unwrap();
1610
1611 let wb2 = Workbook::open(&path).unwrap();
1612 assert_eq!(wb2.get_panes("Combined").unwrap(), Some("A2".to_string()));
1613 let merges = wb2.get_merge_cells("Combined").unwrap();
1614 assert!(merges.contains(&"B1:C1".to_string()));
1615 let w1 = wb2.get_col_width("Combined", "A").unwrap().unwrap();
1616 assert!((w1 - 30.0).abs() < 0.01);
1617 assert_eq!(
1618 wb2.get_cell_value("Combined", "A1").unwrap(),
1619 CellValue::String("Name".to_string())
1620 );
1621 assert_eq!(
1622 wb2.get_cell_value("Combined", "B2").unwrap(),
1623 CellValue::Number(100.0)
1624 );
1625 assert_eq!(
1626 wb2.get_cell_value("Combined", "C2").unwrap(),
1627 CellValue::Bool(true)
1628 );
1629 }
1630
1631 #[test]
1634 fn test_stream_formula_result_types_roundtrip() {
1635 let dir = TempDir::new().unwrap();
1639 let path = dir.path().join("stream_formula_types.xlsx");
1640
1641 let mut wb = Workbook::new();
1642 let mut sw = wb.new_stream_writer("Formulas").unwrap();
1643 sw.write_row(
1644 1,
1645 &[
1646 CellValue::Formula {
1647 expr: "A2&B2".to_string(),
1648 result: Some(Box::new(CellValue::String("hello".to_string()))),
1649 },
1650 CellValue::Formula {
1651 expr: "A2>0".to_string(),
1652 result: Some(Box::new(CellValue::Bool(true))),
1653 },
1654 CellValue::Formula {
1655 expr: "1/0".to_string(),
1656 result: Some(Box::new(CellValue::Error("#DIV/0!".to_string()))),
1657 },
1658 CellValue::Formula {
1659 expr: "SUM(A2:A10)".to_string(),
1660 result: Some(Box::new(CellValue::Number(55.0))),
1661 },
1662 ],
1663 )
1664 .unwrap();
1665 wb.apply_stream_writer(sw).unwrap();
1666 wb.save(&path).unwrap();
1667
1668 let wb2 = Workbook::open(&path).unwrap();
1669 assert_eq!(
1671 wb2.get_cell_value("Formulas", "A1").unwrap(),
1672 CellValue::Formula {
1673 expr: "A2&B2".to_string(),
1674 result: Some(Box::new(CellValue::String("hello".to_string()))),
1675 }
1676 );
1677 assert_eq!(
1679 wb2.get_cell_value("Formulas", "B1").unwrap(),
1680 CellValue::Formula {
1681 expr: "A2>0".to_string(),
1682 result: Some(Box::new(CellValue::Bool(true))),
1683 }
1684 );
1685 assert_eq!(
1687 wb2.get_cell_value("Formulas", "C1").unwrap(),
1688 CellValue::Formula {
1689 expr: "1/0".to_string(),
1690 result: Some(Box::new(CellValue::Error("#DIV/0!".to_string()))),
1691 }
1692 );
1693 assert_eq!(
1695 wb2.get_cell_value("Formulas", "D1").unwrap(),
1696 CellValue::Formula {
1697 expr: "SUM(A2:A10)".to_string(),
1698 result: Some(Box::new(CellValue::Number(55.0))),
1699 }
1700 );
1701 }
1702
1703 #[test]
1704 fn test_stream_edit_after_apply_takes_effect() {
1705 let dir = TempDir::new().unwrap();
1709 let path = dir.path().join("stream_edit_after.xlsx");
1710
1711 let mut wb = Workbook::new();
1712 let mut sw = wb.new_stream_writer("S").unwrap();
1713 sw.write_row(1, &[CellValue::from("old")]).unwrap();
1714 wb.apply_stream_writer(sw).unwrap();
1715
1716 wb.set_cell_value("S", "A1", "new").unwrap();
1718 wb.save(&path).unwrap();
1719
1720 let wb2 = Workbook::open(&path).unwrap();
1721 assert_eq!(
1722 wb2.get_cell_value("S", "A1").unwrap(),
1723 CellValue::String("new".to_string())
1724 );
1725 }
1726
1727 #[test]
1728 fn test_stream_copy_sheet_preserves_data() {
1729 let dir = TempDir::new().unwrap();
1732 let path = dir.path().join("stream_copy.xlsx");
1733
1734 let mut wb = Workbook::new();
1735 let mut sw = wb.new_stream_writer("Src").unwrap();
1736 sw.write_row(1, &[CellValue::from("x")]).unwrap();
1737 sw.write_row(2, &[CellValue::from("y")]).unwrap();
1738 wb.apply_stream_writer(sw).unwrap();
1739
1740 wb.copy_sheet("Src", "Dst").unwrap();
1741 wb.save(&path).unwrap();
1742
1743 let wb2 = Workbook::open(&path).unwrap();
1744 assert_eq!(
1745 wb2.get_cell_value("Src", "A1").unwrap(),
1746 CellValue::String("x".to_string())
1747 );
1748 assert_eq!(
1749 wb2.get_cell_value("Src", "A2").unwrap(),
1750 CellValue::String("y".to_string())
1751 );
1752 assert_eq!(
1753 wb2.get_cell_value("Dst", "A1").unwrap(),
1754 CellValue::String("x".to_string())
1755 );
1756 assert_eq!(
1757 wb2.get_cell_value("Dst", "A2").unwrap(),
1758 CellValue::String("y".to_string())
1759 );
1760 }
1761}