Skip to main content

sheetkit_core/workbook/
sheet_ops.rs

1use super::*;
2
3impl Workbook {
4    /// Return the names of all sheets in workbook order.
5    pub fn sheet_names(&self) -> Vec<&str> {
6        self.worksheets
7            .iter()
8            .map(|(name, _)| name.as_str())
9            .collect()
10    }
11
12    /// Create a new empty sheet with the given name. Returns the 0-based sheet index.
13    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        self.rebuild_sheet_index();
32        Ok(idx)
33    }
34
35    /// Delete a sheet by name.
36    pub fn delete_sheet(&mut self, name: &str) -> Result<()> {
37        let idx = self.sheet_index(name)?;
38        crate::sheet::delete_sheet(
39            &mut self.workbook_xml,
40            &mut self.workbook_rels,
41            &mut self.content_types,
42            &mut self.worksheets,
43            name,
44        )?;
45
46        if idx < self.sheet_comments.len() {
47            self.sheet_comments.remove(idx);
48        }
49        if idx < self.sheet_sparklines.len() {
50            self.sheet_sparklines.remove(idx);
51        }
52        if idx < self.sheet_vml.len() {
53            self.sheet_vml.remove(idx);
54        }
55        self.reindex_sheet_maps_after_delete(idx);
56        self.rebuild_sheet_index();
57        Ok(())
58    }
59
60    /// Rename a sheet.
61    pub fn set_sheet_name(&mut self, old_name: &str, new_name: &str) -> Result<()> {
62        crate::sheet::rename_sheet(
63            &mut self.workbook_xml,
64            &mut self.worksheets,
65            old_name,
66            new_name,
67        )?;
68        self.rebuild_sheet_index();
69        Ok(())
70    }
71
72    /// Copy a sheet, returning the 0-based index of the new copy.
73    pub fn copy_sheet(&mut self, source: &str, target: &str) -> Result<usize> {
74        let idx = crate::sheet::copy_sheet(
75            &mut self.workbook_xml,
76            &mut self.workbook_rels,
77            &mut self.content_types,
78            &mut self.worksheets,
79            source,
80            target,
81        )?;
82        if self.sheet_comments.len() < self.worksheets.len() {
83            self.sheet_comments.push(None);
84        }
85        let source_sparklines = {
86            let src_idx = self.sheet_index(source).unwrap_or(0);
87            self.sheet_sparklines
88                .get(src_idx)
89                .cloned()
90                .unwrap_or_default()
91        };
92        if self.sheet_sparklines.len() < self.worksheets.len() {
93            self.sheet_sparklines.push(source_sparklines);
94        }
95        if self.sheet_vml.len() < self.worksheets.len() {
96            self.sheet_vml.push(None);
97        }
98        self.rebuild_sheet_index();
99        Ok(idx)
100    }
101
102    /// Get a sheet's 0-based index by name. Returns `None` if not found.
103    pub fn get_sheet_index(&self, name: &str) -> Option<usize> {
104        crate::sheet::find_sheet_index(&self.worksheets, name)
105    }
106
107    /// Get the name of the active sheet.
108    pub fn get_active_sheet(&self) -> &str {
109        let idx = crate::sheet::active_sheet_index(&self.workbook_xml);
110        self.worksheets
111            .get(idx)
112            .map(|(n, _)| n.as_str())
113            .unwrap_or_else(|| self.worksheets[0].0.as_str())
114    }
115
116    /// Set the active sheet by name.
117    pub fn set_active_sheet(&mut self, name: &str) -> Result<()> {
118        let idx = crate::sheet::find_sheet_index(&self.worksheets, name).ok_or_else(|| {
119            Error::SheetNotFound {
120                name: name.to_string(),
121            }
122        })?;
123        crate::sheet::set_active_sheet_index(&mut self.workbook_xml, idx as u32);
124        Ok(())
125    }
126
127    /// Create a [`StreamWriter`](crate::stream::StreamWriter) for a new sheet.
128    ///
129    /// The sheet will be added to the workbook when the StreamWriter is applied
130    /// via [`apply_stream_writer`](Self::apply_stream_writer).
131    pub fn new_stream_writer(&self, sheet_name: &str) -> Result<crate::stream::StreamWriter> {
132        crate::sheet::validate_sheet_name(sheet_name)?;
133        if self.worksheets.iter().any(|(n, _)| n == sheet_name) {
134            return Err(Error::SheetAlreadyExists {
135                name: sheet_name.to_string(),
136            });
137        }
138        Ok(crate::stream::StreamWriter::new(sheet_name))
139    }
140
141    /// Apply a completed [`StreamWriter`](crate::stream::StreamWriter) to the
142    /// workbook, adding it as a new sheet.
143    ///
144    /// Returns the 0-based index of the new sheet.
145    pub fn apply_stream_writer(&mut self, writer: crate::stream::StreamWriter) -> Result<usize> {
146        let sheet_name = writer.sheet_name().to_string();
147        let (mut ws, sst) = writer.into_worksheet_parts()?;
148
149        // Merge SST entries and build index mapping (old_index -> new_index)
150        let mut sst_remap: Vec<usize> = Vec::with_capacity(sst.len());
151        for i in 0..sst.len() {
152            if let Some(s) = sst.get(i) {
153                let new_idx = self.sst_runtime.add(s);
154                sst_remap.push(new_idx);
155            }
156        }
157
158        // Remap SST indices in the worksheet cells
159        for row in &mut ws.sheet_data.rows {
160            for cell in &mut row.cells {
161                if cell.t == CellTypeTag::SharedString {
162                    if let Some(ref v) = cell.v {
163                        if let Ok(old_idx) = v.parse::<usize>() {
164                            if let Some(&new_idx) = sst_remap.get(old_idx) {
165                                cell.v = Some(new_idx.to_string());
166                            }
167                        }
168                    }
169                }
170            }
171        }
172
173        // Cell.col is already set by StreamWriter, rows are already in ascending
174        // order, and cells are already in column order -- no sorting needed.
175
176        let idx = crate::sheet::add_sheet(
177            &mut self.workbook_xml,
178            &mut self.workbook_rels,
179            &mut self.content_types,
180            &mut self.worksheets,
181            &sheet_name,
182            ws,
183        )?;
184        if self.sheet_comments.len() < self.worksheets.len() {
185            self.sheet_comments.push(None);
186        }
187        if self.sheet_sparklines.len() < self.worksheets.len() {
188            self.sheet_sparklines.push(vec![]);
189        }
190        if self.sheet_vml.len() < self.worksheets.len() {
191            self.sheet_vml.push(None);
192        }
193        self.rebuild_sheet_index();
194        Ok(idx)
195    }
196
197    /// Insert `count` empty rows starting at `start_row` in the named sheet.
198    pub fn insert_rows(&mut self, sheet: &str, start_row: u32, count: u32) -> Result<()> {
199        let sheet_idx = self.sheet_index(sheet)?;
200        {
201            let ws = &mut self.worksheets[sheet_idx].1;
202            crate::row::insert_rows(ws, start_row, count)?;
203        }
204        self.apply_reference_shift_for_sheet(sheet_idx, |col, row| {
205            if row >= start_row {
206                (col, row + count)
207            } else {
208                (col, row)
209            }
210        })
211    }
212
213    /// Remove a single row from the named sheet, shifting rows below it up.
214    pub fn remove_row(&mut self, sheet: &str, row: u32) -> Result<()> {
215        let sheet_idx = self.sheet_index(sheet)?;
216        {
217            let ws = &mut self.worksheets[sheet_idx].1;
218            crate::row::remove_row(ws, row)?;
219        }
220        self.apply_reference_shift_for_sheet(sheet_idx, |col, r| {
221            if r > row {
222                (col, r - 1)
223            } else {
224                (col, r)
225            }
226        })
227    }
228
229    /// Duplicate a row, inserting the copy directly below.
230    pub fn duplicate_row(&mut self, sheet: &str, row: u32) -> Result<()> {
231        let ws = self.worksheet_mut(sheet)?;
232        crate::row::duplicate_row(ws, row)
233    }
234
235    /// Set the height of a row in points.
236    pub fn set_row_height(&mut self, sheet: &str, row: u32, height: f64) -> Result<()> {
237        let ws = self.worksheet_mut(sheet)?;
238        crate::row::set_row_height(ws, row, height)
239    }
240
241    /// Get the height of a row.
242    pub fn get_row_height(&self, sheet: &str, row: u32) -> Result<Option<f64>> {
243        let ws = self.worksheet_ref(sheet)?;
244        Ok(crate::row::get_row_height(ws, row))
245    }
246
247    /// Set the visibility of a row.
248    pub fn set_row_visible(&mut self, sheet: &str, row: u32, visible: bool) -> Result<()> {
249        let ws = self.worksheet_mut(sheet)?;
250        crate::row::set_row_visible(ws, row, visible)
251    }
252
253    /// Get the visibility of a row. Returns true if visible (not hidden).
254    pub fn get_row_visible(&self, sheet: &str, row: u32) -> Result<bool> {
255        let ws = self.worksheet_ref(sheet)?;
256        Ok(crate::row::get_row_visible(ws, row))
257    }
258
259    /// Set the outline level of a row.
260    pub fn set_row_outline_level(&mut self, sheet: &str, row: u32, level: u8) -> Result<()> {
261        let ws = self.worksheet_mut(sheet)?;
262        crate::row::set_row_outline_level(ws, row, level)
263    }
264
265    /// Get the outline level of a row. Returns 0 if not set.
266    pub fn get_row_outline_level(&self, sheet: &str, row: u32) -> Result<u8> {
267        let ws = self.worksheet_ref(sheet)?;
268        Ok(crate::row::get_row_outline_level(ws, row))
269    }
270
271    /// Set the style for an entire row.
272    ///
273    /// The `style_id` must be a valid index in cellXfs (returned by `add_style`).
274    pub fn set_row_style(&mut self, sheet: &str, row: u32, style_id: u32) -> Result<()> {
275        if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
276            return Err(Error::StyleNotFound { id: style_id });
277        }
278        let ws = self.worksheet_mut(sheet)?;
279        crate::row::set_row_style(ws, row, style_id)
280    }
281
282    /// Get the style ID for a row. Returns 0 (default) if not set.
283    pub fn get_row_style(&self, sheet: &str, row: u32) -> Result<u32> {
284        let ws = self.worksheet_ref(sheet)?;
285        Ok(crate::row::get_row_style(ws, row))
286    }
287
288    /// Get all rows with their data from a sheet.
289    ///
290    /// Returns a Vec of `(row_number, Vec<(column_number, CellValue)>)` tuples.
291    /// Column numbers are 1-based (A=1, B=2, ...). Only rows that contain at
292    /// least one cell are included (sparse).
293    #[allow(clippy::type_complexity)]
294    pub fn get_rows(&self, sheet: &str) -> Result<Vec<(u32, Vec<(u32, CellValue)>)>> {
295        let ws = self.worksheet_ref(sheet)?;
296        crate::row::get_rows(ws, &self.sst_runtime)
297    }
298
299    /// Get all columns with their data from a sheet.
300    ///
301    /// Returns a Vec of `(column_name, Vec<(row_number, CellValue)>)` tuples.
302    /// Only columns that have data are included (sparse).
303    #[allow(clippy::type_complexity)]
304    pub fn get_cols(&self, sheet: &str) -> Result<Vec<(String, Vec<(u32, CellValue)>)>> {
305        let ws = self.worksheet_ref(sheet)?;
306        crate::col::get_cols(ws, &self.sst_runtime)
307    }
308
309    /// Set the width of a column.
310    pub fn set_col_width(&mut self, sheet: &str, col: &str, width: f64) -> Result<()> {
311        let ws = self.worksheet_mut(sheet)?;
312        crate::col::set_col_width(ws, col, width)
313    }
314
315    /// Get the width of a column.
316    pub fn get_col_width(&self, sheet: &str, col: &str) -> Result<Option<f64>> {
317        let ws = self.worksheet_ref(sheet)?;
318        Ok(crate::col::get_col_width(ws, col))
319    }
320
321    /// Set the visibility of a column.
322    pub fn set_col_visible(&mut self, sheet: &str, col: &str, visible: bool) -> Result<()> {
323        let ws = self.worksheet_mut(sheet)?;
324        crate::col::set_col_visible(ws, col, visible)
325    }
326
327    /// Get the visibility of a column. Returns true if visible (not hidden).
328    pub fn get_col_visible(&self, sheet: &str, col: &str) -> Result<bool> {
329        let ws = self.worksheet_ref(sheet)?;
330        crate::col::get_col_visible(ws, col)
331    }
332
333    /// Set the outline level of a column.
334    pub fn set_col_outline_level(&mut self, sheet: &str, col: &str, level: u8) -> Result<()> {
335        let ws = self.worksheet_mut(sheet)?;
336        crate::col::set_col_outline_level(ws, col, level)
337    }
338
339    /// Get the outline level of a column. Returns 0 if not set.
340    pub fn get_col_outline_level(&self, sheet: &str, col: &str) -> Result<u8> {
341        let ws = self.worksheet_ref(sheet)?;
342        crate::col::get_col_outline_level(ws, col)
343    }
344
345    /// Set the style for an entire column.
346    ///
347    /// The `style_id` must be a valid index in cellXfs (returned by `add_style`).
348    pub fn set_col_style(&mut self, sheet: &str, col: &str, style_id: u32) -> Result<()> {
349        if style_id as usize >= self.stylesheet.cell_xfs.xfs.len() {
350            return Err(Error::StyleNotFound { id: style_id });
351        }
352        let ws = self.worksheet_mut(sheet)?;
353        crate::col::set_col_style(ws, col, style_id)
354    }
355
356    /// Get the style ID for a column. Returns 0 (default) if not set.
357    pub fn get_col_style(&self, sheet: &str, col: &str) -> Result<u32> {
358        let ws = self.worksheet_ref(sheet)?;
359        crate::col::get_col_style(ws, col)
360    }
361
362    /// Insert `count` columns starting at `col` in the named sheet.
363    pub fn insert_cols(&mut self, sheet: &str, col: &str, count: u32) -> Result<()> {
364        let sheet_idx = self.sheet_index(sheet)?;
365        let start_col = column_name_to_number(col)?;
366        {
367            let ws = &mut self.worksheets[sheet_idx].1;
368            crate::col::insert_cols(ws, col, count)?;
369        }
370        self.apply_reference_shift_for_sheet(sheet_idx, |c, row| {
371            if c >= start_col {
372                (c + count, row)
373            } else {
374                (c, row)
375            }
376        })
377    }
378
379    /// Remove a single column from the named sheet.
380    pub fn remove_col(&mut self, sheet: &str, col: &str) -> Result<()> {
381        let sheet_idx = self.sheet_index(sheet)?;
382        let col_num = column_name_to_number(col)?;
383        {
384            let ws = &mut self.worksheets[sheet_idx].1;
385            crate::col::remove_col(ws, col)?;
386        }
387        self.apply_reference_shift_for_sheet(sheet_idx, |c, row| {
388            if c > col_num {
389                (c - 1, row)
390            } else {
391                (c, row)
392            }
393        })
394    }
395
396    /// Reindex per-sheet maps after deleting a sheet.
397    pub(crate) fn reindex_sheet_maps_after_delete(&mut self, removed_idx: usize) {
398        self.worksheet_rels = self
399            .worksheet_rels
400            .iter()
401            .filter_map(|(idx, rels)| {
402                if *idx == removed_idx {
403                    None
404                } else if *idx > removed_idx {
405                    Some((idx - 1, rels.clone()))
406                } else {
407                    Some((*idx, rels.clone()))
408                }
409            })
410            .collect();
411
412        self.worksheet_drawings = self
413            .worksheet_drawings
414            .iter()
415            .filter_map(|(idx, drawing_idx)| {
416                if *idx == removed_idx {
417                    None
418                } else if *idx > removed_idx {
419                    Some((idx - 1, *drawing_idx))
420                } else {
421                    Some((*idx, *drawing_idx))
422                }
423            })
424            .collect();
425    }
426
427    /// Apply a cell-reference shift transformation to sheet-scoped structures.
428    pub(crate) fn apply_reference_shift_for_sheet<F>(
429        &mut self,
430        sheet_idx: usize,
431        shift_cell: F,
432    ) -> Result<()>
433    where
434        F: Fn(u32, u32) -> (u32, u32) + Copy,
435    {
436        {
437            let ws = &mut self.worksheets[sheet_idx].1;
438
439            // Cell formulas.
440            for row in &mut ws.sheet_data.rows {
441                for cell in &mut row.cells {
442                    if let Some(ref mut f) = cell.f {
443                        if let Some(ref mut expr) = f.value {
444                            *expr = shift_cell_references_in_text(expr, shift_cell)?;
445                        }
446                    }
447                }
448            }
449
450            // Merged ranges.
451            if let Some(ref mut merges) = ws.merge_cells {
452                for mc in &mut merges.merge_cells {
453                    mc.reference = shift_cell_references_in_text(&mc.reference, shift_cell)?;
454                }
455            }
456
457            // Auto-filter.
458            if let Some(ref mut af) = ws.auto_filter {
459                af.reference = shift_cell_references_in_text(&af.reference, shift_cell)?;
460            }
461
462            // Data validations.
463            if let Some(ref mut dvs) = ws.data_validations {
464                for dv in &mut dvs.data_validations {
465                    dv.sqref = shift_cell_references_in_text(&dv.sqref, shift_cell)?;
466                    if let Some(ref mut f1) = dv.formula1 {
467                        *f1 = shift_cell_references_in_text(f1, shift_cell)?;
468                    }
469                    if let Some(ref mut f2) = dv.formula2 {
470                        *f2 = shift_cell_references_in_text(f2, shift_cell)?;
471                    }
472                }
473            }
474
475            // Conditional formatting ranges/formulas.
476            for cf in &mut ws.conditional_formatting {
477                cf.sqref = shift_cell_references_in_text(&cf.sqref, shift_cell)?;
478                for rule in &mut cf.cf_rules {
479                    for f in &mut rule.formulas {
480                        *f = shift_cell_references_in_text(f, shift_cell)?;
481                    }
482                }
483            }
484
485            // Hyperlinks.
486            if let Some(ref mut hyperlinks) = ws.hyperlinks {
487                for hl in &mut hyperlinks.hyperlinks {
488                    hl.reference = shift_cell_references_in_text(&hl.reference, shift_cell)?;
489                    if let Some(ref mut loc) = hl.location {
490                        *loc = shift_cell_references_in_text(loc, shift_cell)?;
491                    }
492                }
493            }
494
495            // Pane/selection references.
496            if let Some(ref mut views) = ws.sheet_views {
497                for view in &mut views.sheet_views {
498                    if let Some(ref mut pane) = view.pane {
499                        if let Some(ref mut top_left) = pane.top_left_cell {
500                            *top_left = shift_cell_references_in_text(top_left, shift_cell)?;
501                        }
502                    }
503                    for sel in &mut view.selection {
504                        if let Some(ref mut ac) = sel.active_cell {
505                            *ac = shift_cell_references_in_text(ac, shift_cell)?;
506                        }
507                        if let Some(ref mut sqref) = sel.sqref {
508                            *sqref = shift_cell_references_in_text(sqref, shift_cell)?;
509                        }
510                    }
511                }
512            }
513        }
514
515        // Drawing anchors attached to this sheet.
516        if let Some(&drawing_idx) = self.worksheet_drawings.get(&sheet_idx) {
517            if let Some((_, drawing)) = self.drawings.get_mut(drawing_idx) {
518                for anchor in &mut drawing.one_cell_anchors {
519                    let (new_col, new_row) = shift_cell(anchor.from.col + 1, anchor.from.row + 1);
520                    anchor.from.col = new_col - 1;
521                    anchor.from.row = new_row - 1;
522                }
523                for anchor in &mut drawing.two_cell_anchors {
524                    let (from_col, from_row) = shift_cell(anchor.from.col + 1, anchor.from.row + 1);
525                    anchor.from.col = from_col - 1;
526                    anchor.from.row = from_row - 1;
527                    let (to_col, to_row) = shift_cell(anchor.to.col + 1, anchor.to.row + 1);
528                    anchor.to.col = to_col - 1;
529                    anchor.to.row = to_row - 1;
530                }
531            }
532        }
533
534        Ok(())
535    }
536
537    /// Ensure a drawing exists for the given sheet index, creating one if needed.
538    /// Returns the drawing index.
539    pub(crate) fn ensure_drawing_for_sheet(&mut self, sheet_idx: usize) -> usize {
540        if let Some(&idx) = self.worksheet_drawings.get(&sheet_idx) {
541            return idx;
542        }
543
544        let idx = self.drawings.len();
545        let drawing_path = format!("xl/drawings/drawing{}.xml", idx + 1);
546        self.drawings.push((drawing_path, WsDr::default()));
547        self.worksheet_drawings.insert(sheet_idx, idx);
548
549        // Add drawing reference to the worksheet.
550        let ws_rid = self.next_worksheet_rid(sheet_idx);
551        self.worksheets[sheet_idx].1.drawing = Some(DrawingRef {
552            r_id: ws_rid.clone(),
553        });
554
555        // Add worksheet->drawing relationship.
556        let drawing_rel_target = format!("../drawings/drawing{}.xml", idx + 1);
557        let ws_rels = self
558            .worksheet_rels
559            .entry(sheet_idx)
560            .or_insert_with(|| Relationships {
561                xmlns: sheetkit_xml::namespaces::PACKAGE_RELATIONSHIPS.to_string(),
562                relationships: vec![],
563            });
564        ws_rels.relationships.push(Relationship {
565            id: ws_rid,
566            rel_type: rel_types::DRAWING.to_string(),
567            target: drawing_rel_target,
568            target_mode: None,
569        });
570
571        // Add content type for the drawing.
572        self.content_types.overrides.push(ContentTypeOverride {
573            part_name: format!("/xl/drawings/drawing{}.xml", idx + 1),
574            content_type: mime_types::DRAWING.to_string(),
575        });
576
577        idx
578    }
579
580    /// Generate the next relationship ID for a worksheet's rels.
581    pub(crate) fn next_worksheet_rid(&self, sheet_idx: usize) -> String {
582        let existing = self
583            .worksheet_rels
584            .get(&sheet_idx)
585            .map(|r| r.relationships.as_slice())
586            .unwrap_or(&[]);
587        crate::sheet::next_rid(existing)
588    }
589
590    /// Generate the next relationship ID for a drawing's rels.
591    pub(crate) fn next_drawing_rid(&self, drawing_idx: usize) -> String {
592        let existing = self
593            .drawing_rels
594            .get(&drawing_idx)
595            .map(|r| r.relationships.as_slice())
596            .unwrap_or(&[]);
597        crate::sheet::next_rid(existing)
598    }
599}
600
601#[cfg(test)]
602mod tests {
603    use super::*;
604    use tempfile::TempDir;
605
606    #[test]
607    fn test_new_sheet_basic() {
608        let mut wb = Workbook::new();
609        let idx = wb.new_sheet("Sheet2").unwrap();
610        assert_eq!(idx, 1);
611        assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet2"]);
612    }
613
614    #[test]
615    fn test_new_sheet_duplicate_returns_error() {
616        let mut wb = Workbook::new();
617        let result = wb.new_sheet("Sheet1");
618        assert!(result.is_err());
619        assert!(matches!(
620            result.unwrap_err(),
621            Error::SheetAlreadyExists { .. }
622        ));
623    }
624
625    #[test]
626    fn test_new_sheet_invalid_name_returns_error() {
627        let mut wb = Workbook::new();
628        let result = wb.new_sheet("Bad/Name");
629        assert!(result.is_err());
630        assert!(matches!(result.unwrap_err(), Error::InvalidSheetName(_)));
631    }
632
633    #[test]
634    fn test_delete_sheet_basic() {
635        let mut wb = Workbook::new();
636        wb.new_sheet("Sheet2").unwrap();
637        wb.delete_sheet("Sheet1").unwrap();
638        assert_eq!(wb.sheet_names(), vec!["Sheet2"]);
639    }
640
641    #[test]
642    fn test_delete_last_sheet_returns_error() {
643        let mut wb = Workbook::new();
644        let result = wb.delete_sheet("Sheet1");
645        assert!(result.is_err());
646    }
647
648    #[test]
649    fn test_delete_nonexistent_sheet_returns_error() {
650        let mut wb = Workbook::new();
651        let result = wb.delete_sheet("NoSuchSheet");
652        assert!(result.is_err());
653        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
654    }
655
656    #[test]
657    fn test_set_sheet_name_basic() {
658        let mut wb = Workbook::new();
659        wb.set_sheet_name("Sheet1", "Renamed").unwrap();
660        assert_eq!(wb.sheet_names(), vec!["Renamed"]);
661    }
662
663    #[test]
664    fn test_set_sheet_name_to_existing_returns_error() {
665        let mut wb = Workbook::new();
666        wb.new_sheet("Sheet2").unwrap();
667        let result = wb.set_sheet_name("Sheet1", "Sheet2");
668        assert!(result.is_err());
669        assert!(matches!(
670            result.unwrap_err(),
671            Error::SheetAlreadyExists { .. }
672        ));
673    }
674
675    #[test]
676    fn test_copy_sheet_basic() {
677        let mut wb = Workbook::new();
678        let idx = wb.copy_sheet("Sheet1", "Sheet1 Copy").unwrap();
679        assert_eq!(idx, 1);
680        assert_eq!(wb.sheet_names(), vec!["Sheet1", "Sheet1 Copy"]);
681    }
682
683    #[test]
684    fn test_get_sheet_index() {
685        let mut wb = Workbook::new();
686        wb.new_sheet("Sheet2").unwrap();
687        assert_eq!(wb.get_sheet_index("Sheet1"), Some(0));
688        assert_eq!(wb.get_sheet_index("Sheet2"), Some(1));
689        assert_eq!(wb.get_sheet_index("Nonexistent"), None);
690    }
691
692    #[test]
693    fn test_get_active_sheet_default() {
694        let wb = Workbook::new();
695        assert_eq!(wb.get_active_sheet(), "Sheet1");
696    }
697
698    #[test]
699    fn test_set_active_sheet() {
700        let mut wb = Workbook::new();
701        wb.new_sheet("Sheet2").unwrap();
702        wb.set_active_sheet("Sheet2").unwrap();
703        assert_eq!(wb.get_active_sheet(), "Sheet2");
704    }
705
706    #[test]
707    fn test_set_active_sheet_not_found() {
708        let mut wb = Workbook::new();
709        let result = wb.set_active_sheet("NoSuchSheet");
710        assert!(result.is_err());
711        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
712    }
713
714    #[test]
715    fn test_sheet_management_roundtrip_save_open() {
716        let dir = TempDir::new().unwrap();
717        let path = dir.path().join("sheet_mgmt.xlsx");
718
719        let mut wb = Workbook::new();
720        wb.new_sheet("Data").unwrap();
721        wb.new_sheet("Summary").unwrap();
722        wb.set_sheet_name("Sheet1", "Overview").unwrap();
723        wb.save(&path).unwrap();
724
725        let wb2 = Workbook::open(&path).unwrap();
726        assert_eq!(wb2.sheet_names(), vec!["Overview", "Data", "Summary"]);
727    }
728
729    #[test]
730    fn test_workbook_insert_rows() {
731        let mut wb = Workbook::new();
732        wb.set_cell_value("Sheet1", "A1", "stay").unwrap();
733        wb.set_cell_value("Sheet1", "A2", "shift").unwrap();
734        wb.insert_rows("Sheet1", 2, 1).unwrap();
735
736        assert_eq!(
737            wb.get_cell_value("Sheet1", "A1").unwrap(),
738            CellValue::String("stay".to_string())
739        );
740        assert_eq!(
741            wb.get_cell_value("Sheet1", "A3").unwrap(),
742            CellValue::String("shift".to_string())
743        );
744        assert_eq!(wb.get_cell_value("Sheet1", "A2").unwrap(), CellValue::Empty);
745    }
746
747    #[test]
748    fn test_workbook_insert_rows_updates_formula_and_ranges() {
749        let mut wb = Workbook::new();
750        wb.set_cell_value(
751            "Sheet1",
752            "C1",
753            CellValue::Formula {
754                expr: "SUM(A2:B2)".to_string(),
755                result: None,
756            },
757        )
758        .unwrap();
759        wb.add_data_validation(
760            "Sheet1",
761            &crate::validation::DataValidationConfig::whole_number("A2:A5", 1, 9),
762        )
763        .unwrap();
764        wb.set_auto_filter("Sheet1", "A2:B10").unwrap();
765        wb.merge_cells("Sheet1", "A2", "B3").unwrap();
766
767        wb.insert_rows("Sheet1", 2, 1).unwrap();
768
769        match wb.get_cell_value("Sheet1", "C1").unwrap() {
770            CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A3:B3)"),
771            other => panic!("expected formula, got {other:?}"),
772        }
773
774        let validations = wb.get_data_validations("Sheet1").unwrap();
775        assert_eq!(validations.len(), 1);
776        assert_eq!(validations[0].sqref, "A3:A6");
777
778        let merges = wb.get_merge_cells("Sheet1").unwrap();
779        assert_eq!(merges, vec!["A3:B4".to_string()]);
780
781        let ws = wb.worksheet_ref("Sheet1").unwrap();
782        assert_eq!(ws.auto_filter.as_ref().unwrap().reference, "A3:B11");
783    }
784
785    #[test]
786    fn test_workbook_insert_rows_sheet_not_found() {
787        let mut wb = Workbook::new();
788        let result = wb.insert_rows("NoSheet", 1, 1);
789        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
790    }
791
792    #[test]
793    fn test_workbook_remove_row() {
794        let mut wb = Workbook::new();
795        wb.set_cell_value("Sheet1", "A1", "first").unwrap();
796        wb.set_cell_value("Sheet1", "A2", "second").unwrap();
797        wb.set_cell_value("Sheet1", "A3", "third").unwrap();
798        wb.remove_row("Sheet1", 2).unwrap();
799
800        assert_eq!(
801            wb.get_cell_value("Sheet1", "A1").unwrap(),
802            CellValue::String("first".to_string())
803        );
804        assert_eq!(
805            wb.get_cell_value("Sheet1", "A2").unwrap(),
806            CellValue::String("third".to_string())
807        );
808    }
809
810    #[test]
811    fn test_workbook_remove_row_sheet_not_found() {
812        let mut wb = Workbook::new();
813        let result = wb.remove_row("NoSheet", 1);
814        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
815    }
816
817    #[test]
818    fn test_workbook_duplicate_row() {
819        let mut wb = Workbook::new();
820        wb.set_cell_value("Sheet1", "A1", "original").unwrap();
821        wb.duplicate_row("Sheet1", 1).unwrap();
822
823        assert_eq!(
824            wb.get_cell_value("Sheet1", "A1").unwrap(),
825            CellValue::String("original".to_string())
826        );
827        // The duplicated row at row 2 has the same SST index.
828        assert_eq!(
829            wb.get_cell_value("Sheet1", "A2").unwrap(),
830            CellValue::String("original".to_string())
831        );
832    }
833
834    #[test]
835    fn test_workbook_set_and_get_row_height() {
836        let mut wb = Workbook::new();
837        wb.set_row_height("Sheet1", 3, 25.0).unwrap();
838        assert_eq!(wb.get_row_height("Sheet1", 3).unwrap(), Some(25.0));
839    }
840
841    #[test]
842    fn test_workbook_get_row_height_sheet_not_found() {
843        let wb = Workbook::new();
844        let result = wb.get_row_height("NoSheet", 1);
845        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
846    }
847
848    #[test]
849    fn test_workbook_set_row_visible() {
850        let mut wb = Workbook::new();
851        wb.set_row_visible("Sheet1", 1, false).unwrap();
852    }
853
854    #[test]
855    fn test_workbook_set_row_visible_sheet_not_found() {
856        let mut wb = Workbook::new();
857        let result = wb.set_row_visible("NoSheet", 1, false);
858        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
859    }
860
861    #[test]
862    fn test_workbook_set_and_get_col_width() {
863        let mut wb = Workbook::new();
864        wb.set_col_width("Sheet1", "A", 18.0).unwrap();
865        assert_eq!(wb.get_col_width("Sheet1", "A").unwrap(), Some(18.0));
866    }
867
868    #[test]
869    fn test_workbook_get_col_width_sheet_not_found() {
870        let wb = Workbook::new();
871        let result = wb.get_col_width("NoSheet", "A");
872        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
873    }
874
875    #[test]
876    fn test_workbook_set_col_visible() {
877        let mut wb = Workbook::new();
878        wb.set_col_visible("Sheet1", "B", false).unwrap();
879    }
880
881    #[test]
882    fn test_workbook_set_col_visible_sheet_not_found() {
883        let mut wb = Workbook::new();
884        let result = wb.set_col_visible("NoSheet", "A", false);
885        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
886    }
887
888    #[test]
889    fn test_workbook_insert_cols() {
890        let mut wb = Workbook::new();
891        wb.set_cell_value("Sheet1", "A1", "a").unwrap();
892        wb.set_cell_value("Sheet1", "B1", "b").unwrap();
893        wb.insert_cols("Sheet1", "B", 1).unwrap();
894
895        assert_eq!(
896            wb.get_cell_value("Sheet1", "A1").unwrap(),
897            CellValue::String("a".to_string())
898        );
899        assert_eq!(wb.get_cell_value("Sheet1", "B1").unwrap(), CellValue::Empty);
900        assert_eq!(
901            wb.get_cell_value("Sheet1", "C1").unwrap(),
902            CellValue::String("b".to_string())
903        );
904    }
905
906    #[test]
907    fn test_workbook_insert_cols_updates_formula_and_ranges() {
908        let mut wb = Workbook::new();
909        wb.set_cell_value(
910            "Sheet1",
911            "D1",
912            CellValue::Formula {
913                expr: "SUM(A1:B1)".to_string(),
914                result: None,
915            },
916        )
917        .unwrap();
918        wb.add_data_validation(
919            "Sheet1",
920            &crate::validation::DataValidationConfig::whole_number("B2:C3", 1, 9),
921        )
922        .unwrap();
923        wb.set_auto_filter("Sheet1", "A1:C10").unwrap();
924        wb.merge_cells("Sheet1", "B3", "C4").unwrap();
925
926        wb.insert_cols("Sheet1", "B", 2).unwrap();
927
928        match wb.get_cell_value("Sheet1", "F1").unwrap() {
929            CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(A1:D1)"),
930            other => panic!("expected formula, got {other:?}"),
931        }
932
933        let validations = wb.get_data_validations("Sheet1").unwrap();
934        assert_eq!(validations.len(), 1);
935        assert_eq!(validations[0].sqref, "D2:E3");
936
937        let merges = wb.get_merge_cells("Sheet1").unwrap();
938        assert_eq!(merges, vec!["D3:E4".to_string()]);
939
940        let ws = wb.worksheet_ref("Sheet1").unwrap();
941        assert_eq!(ws.auto_filter.as_ref().unwrap().reference, "A1:E10");
942    }
943
944    #[test]
945    fn test_workbook_insert_cols_sheet_not_found() {
946        let mut wb = Workbook::new();
947        let result = wb.insert_cols("NoSheet", "A", 1);
948        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
949    }
950
951    #[test]
952    fn test_workbook_remove_col() {
953        let mut wb = Workbook::new();
954        wb.set_cell_value("Sheet1", "A1", "a").unwrap();
955        wb.set_cell_value("Sheet1", "B1", "b").unwrap();
956        wb.set_cell_value("Sheet1", "C1", "c").unwrap();
957        wb.remove_col("Sheet1", "B").unwrap();
958
959        assert_eq!(
960            wb.get_cell_value("Sheet1", "A1").unwrap(),
961            CellValue::String("a".to_string())
962        );
963        assert_eq!(
964            wb.get_cell_value("Sheet1", "B1").unwrap(),
965            CellValue::String("c".to_string())
966        );
967    }
968
969    #[test]
970    fn test_workbook_remove_col_sheet_not_found() {
971        let mut wb = Workbook::new();
972        let result = wb.remove_col("NoSheet", "A");
973        assert!(matches!(result.unwrap_err(), Error::SheetNotFound { .. }));
974    }
975
976    #[test]
977    fn test_new_stream_writer_validates_name() {
978        let wb = Workbook::new();
979        let result = wb.new_stream_writer("Bad[Name");
980        assert!(result.is_err());
981        assert!(matches!(result.unwrap_err(), Error::InvalidSheetName(_)));
982    }
983
984    #[test]
985    fn test_new_stream_writer_rejects_duplicate() {
986        let wb = Workbook::new();
987        let result = wb.new_stream_writer("Sheet1");
988        assert!(result.is_err());
989        assert!(matches!(
990            result.unwrap_err(),
991            Error::SheetAlreadyExists { .. }
992        ));
993    }
994
995    #[test]
996    fn test_new_stream_writer_valid_name() {
997        let wb = Workbook::new();
998        let sw = wb.new_stream_writer("StreamSheet").unwrap();
999        assert_eq!(sw.sheet_name(), "StreamSheet");
1000    }
1001
1002    #[test]
1003    fn test_apply_stream_writer_adds_sheet() {
1004        let mut wb = Workbook::new();
1005        let mut sw = wb.new_stream_writer("StreamSheet").unwrap();
1006        sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1007            .unwrap();
1008        let idx = wb.apply_stream_writer(sw).unwrap();
1009        assert_eq!(idx, 1);
1010        assert_eq!(wb.sheet_names(), vec!["Sheet1", "StreamSheet"]);
1011    }
1012
1013    #[test]
1014    fn test_apply_stream_writer_merges_sst() {
1015        let mut wb = Workbook::new();
1016        wb.set_cell_value("Sheet1", "A1", "Existing").unwrap();
1017
1018        let mut sw = wb.new_stream_writer("StreamSheet").unwrap();
1019        sw.write_row(1, &[CellValue::from("New"), CellValue::from("Existing")])
1020            .unwrap();
1021        wb.apply_stream_writer(sw).unwrap();
1022
1023        assert!(wb.sst_runtime.len() >= 2);
1024    }
1025
1026    #[test]
1027    fn test_stream_writer_save_and_reopen() {
1028        let dir = TempDir::new().unwrap();
1029        let path = dir.path().join("stream_test.xlsx");
1030
1031        let mut wb = Workbook::new();
1032        wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1033
1034        let mut sw = wb.new_stream_writer("Streamed").unwrap();
1035        sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Value")])
1036            .unwrap();
1037        sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(100)])
1038            .unwrap();
1039        sw.write_row(3, &[CellValue::from("Bob"), CellValue::from(200)])
1040            .unwrap();
1041        wb.apply_stream_writer(sw).unwrap();
1042
1043        wb.save(&path).unwrap();
1044
1045        let wb2 = Workbook::open(&path).unwrap();
1046        assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Streamed"]);
1047        assert_eq!(
1048            wb2.get_cell_value("Sheet1", "A1").unwrap(),
1049            CellValue::String("Normal".to_string())
1050        );
1051        assert_eq!(
1052            wb2.get_cell_value("Streamed", "A1").unwrap(),
1053            CellValue::String("Name".to_string())
1054        );
1055        assert_eq!(
1056            wb2.get_cell_value("Streamed", "B2").unwrap(),
1057            CellValue::Number(100.0)
1058        );
1059        assert_eq!(
1060            wb2.get_cell_value("Streamed", "A3").unwrap(),
1061            CellValue::String("Bob".to_string())
1062        );
1063    }
1064
1065    #[test]
1066    fn test_workbook_get_rows_empty_sheet() {
1067        let wb = Workbook::new();
1068        let rows = wb.get_rows("Sheet1").unwrap();
1069        assert!(rows.is_empty());
1070    }
1071
1072    #[test]
1073    fn test_workbook_get_rows_with_data() {
1074        let mut wb = Workbook::new();
1075        wb.set_cell_value("Sheet1", "A1", "Name").unwrap();
1076        wb.set_cell_value("Sheet1", "B1", 42.0).unwrap();
1077        wb.set_cell_value("Sheet1", "A2", "Alice").unwrap();
1078        wb.set_cell_value("Sheet1", "B2", true).unwrap();
1079
1080        let rows = wb.get_rows("Sheet1").unwrap();
1081        assert_eq!(rows.len(), 2);
1082        assert_eq!(rows[0].0, 1);
1083        assert_eq!(rows[0].1.len(), 2);
1084        assert_eq!(rows[0].1[0].0, 1);
1085        assert_eq!(rows[0].1[0].1, CellValue::String("Name".to_string()));
1086        assert_eq!(rows[0].1[1].0, 2);
1087        assert_eq!(rows[0].1[1].1, CellValue::Number(42.0));
1088        assert_eq!(rows[1].0, 2);
1089        assert_eq!(rows[1].1[0].1, CellValue::String("Alice".to_string()));
1090        assert_eq!(rows[1].1[1].1, CellValue::Bool(true));
1091    }
1092
1093    #[test]
1094    fn test_workbook_get_rows_sheet_not_found() {
1095        let wb = Workbook::new();
1096        assert!(wb.get_rows("NoSheet").is_err());
1097    }
1098
1099    #[test]
1100    fn test_workbook_get_cols_empty_sheet() {
1101        let wb = Workbook::new();
1102        let cols = wb.get_cols("Sheet1").unwrap();
1103        assert!(cols.is_empty());
1104    }
1105
1106    #[test]
1107    fn test_workbook_get_cols_with_data() {
1108        let mut wb = Workbook::new();
1109        wb.set_cell_value("Sheet1", "A1", "Name").unwrap();
1110        wb.set_cell_value("Sheet1", "B1", 42.0).unwrap();
1111        wb.set_cell_value("Sheet1", "A2", "Alice").unwrap();
1112        wb.set_cell_value("Sheet1", "B2", 30.0).unwrap();
1113
1114        let cols = wb.get_cols("Sheet1").unwrap();
1115        assert_eq!(cols.len(), 2);
1116        assert_eq!(cols[0].0, "A");
1117        assert_eq!(cols[0].1.len(), 2);
1118        assert_eq!(cols[0].1[0], (1, CellValue::String("Name".to_string())));
1119        assert_eq!(cols[0].1[1], (2, CellValue::String("Alice".to_string())));
1120        assert_eq!(cols[1].0, "B");
1121        assert_eq!(cols[1].1[0], (1, CellValue::Number(42.0)));
1122        assert_eq!(cols[1].1[1], (2, CellValue::Number(30.0)));
1123    }
1124
1125    #[test]
1126    fn test_workbook_get_cols_sheet_not_found() {
1127        let wb = Workbook::new();
1128        assert!(wb.get_cols("NoSheet").is_err());
1129    }
1130
1131    #[test]
1132    fn test_apply_stream_writer_cells_readable_without_reopen() {
1133        let mut wb = Workbook::new();
1134        let mut sw = wb.new_stream_writer("Streamed").unwrap();
1135        sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Age")])
1136            .unwrap();
1137        sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(30)])
1138            .unwrap();
1139        wb.apply_stream_writer(sw).unwrap();
1140
1141        // Reading cells directly (without save/reopen) must work because
1142        // apply_stream_writer should populate cell.col and sort cells.
1143        assert_eq!(
1144            wb.get_cell_value("Streamed", "A1").unwrap(),
1145            CellValue::String("Name".to_string())
1146        );
1147        assert_eq!(
1148            wb.get_cell_value("Streamed", "B1").unwrap(),
1149            CellValue::String("Age".to_string())
1150        );
1151        assert_eq!(
1152            wb.get_cell_value("Streamed", "A2").unwrap(),
1153            CellValue::String("Alice".to_string())
1154        );
1155        assert_eq!(
1156            wb.get_cell_value("Streamed", "B2").unwrap(),
1157            CellValue::Number(30.0)
1158        );
1159    }
1160
1161    #[test]
1162    fn test_workbook_get_rows_roundtrip_save_open() {
1163        let mut wb = Workbook::new();
1164        wb.set_cell_value("Sheet1", "A1", "hello").unwrap();
1165        wb.set_cell_value("Sheet1", "B1", 99.0).unwrap();
1166        wb.set_cell_value("Sheet1", "A2", true).unwrap();
1167
1168        let tmp = std::env::temp_dir().join("test_get_rows_roundtrip.xlsx");
1169        wb.save(&tmp).unwrap();
1170
1171        let wb2 = Workbook::open(&tmp).unwrap();
1172        let rows = wb2.get_rows("Sheet1").unwrap();
1173        assert_eq!(rows.len(), 2);
1174        assert_eq!(rows[0].1[0].1, CellValue::String("hello".to_string()));
1175        assert_eq!(rows[0].1[1].1, CellValue::Number(99.0));
1176        assert_eq!(rows[1].1[0].1, CellValue::Bool(true));
1177
1178        let _ = std::fs::remove_file(&tmp);
1179    }
1180
1181    #[test]
1182    fn test_apply_stream_optimized_basic() {
1183        let mut wb = Workbook::new();
1184        let mut sw = wb.new_stream_writer("Optimized").unwrap();
1185        sw.write_row(1, &[CellValue::from("Hello"), CellValue::from(42)])
1186            .unwrap();
1187        sw.write_row(2, &[CellValue::from("World"), CellValue::from(99)])
1188            .unwrap();
1189        let idx = wb.apply_stream_writer(sw).unwrap();
1190        assert_eq!(idx, 1);
1191
1192        assert_eq!(
1193            wb.get_cell_value("Optimized", "A1").unwrap(),
1194            CellValue::String("Hello".to_string())
1195        );
1196        assert_eq!(
1197            wb.get_cell_value("Optimized", "B1").unwrap(),
1198            CellValue::Number(42.0)
1199        );
1200        assert_eq!(
1201            wb.get_cell_value("Optimized", "A2").unwrap(),
1202            CellValue::String("World".to_string())
1203        );
1204        assert_eq!(
1205            wb.get_cell_value("Optimized", "B2").unwrap(),
1206            CellValue::Number(99.0)
1207        );
1208    }
1209
1210    #[test]
1211    fn test_apply_stream_optimized_sst_merge() {
1212        let mut wb = Workbook::new();
1213        wb.set_cell_value("Sheet1", "A1", "Existing").unwrap();
1214
1215        let mut sw = wb.new_stream_writer("Streamed").unwrap();
1216        sw.write_row(
1217            1,
1218            &[
1219                CellValue::from("New"),
1220                CellValue::from("Existing"),
1221                CellValue::from("New"),
1222            ],
1223        )
1224        .unwrap();
1225        wb.apply_stream_writer(sw).unwrap();
1226
1227        assert_eq!(
1228            wb.get_cell_value("Streamed", "A1").unwrap(),
1229            CellValue::String("New".to_string())
1230        );
1231        assert_eq!(
1232            wb.get_cell_value("Streamed", "B1").unwrap(),
1233            CellValue::String("Existing".to_string())
1234        );
1235        assert!(wb.sst_runtime.len() >= 2);
1236    }
1237
1238    #[test]
1239    fn test_apply_stream_optimized_all_types() {
1240        let mut wb = Workbook::new();
1241        let mut sw = wb.new_stream_writer("Types").unwrap();
1242        sw.write_row(
1243            1,
1244            &[
1245                CellValue::from("text"),
1246                CellValue::from(42),
1247                CellValue::from(3.14),
1248                CellValue::from(true),
1249                CellValue::Formula {
1250                    expr: "SUM(B1:C1)".to_string(),
1251                    result: None,
1252                },
1253                CellValue::Error("#N/A".to_string()),
1254                CellValue::Empty,
1255            ],
1256        )
1257        .unwrap();
1258        wb.apply_stream_writer(sw).unwrap();
1259
1260        assert_eq!(
1261            wb.get_cell_value("Types", "A1").unwrap(),
1262            CellValue::String("text".to_string())
1263        );
1264        assert_eq!(
1265            wb.get_cell_value("Types", "B1").unwrap(),
1266            CellValue::Number(42.0)
1267        );
1268        assert_eq!(
1269            wb.get_cell_value("Types", "D1").unwrap(),
1270            CellValue::Bool(true)
1271        );
1272        match wb.get_cell_value("Types", "E1").unwrap() {
1273            CellValue::Formula { expr, .. } => assert_eq!(expr, "SUM(B1:C1)"),
1274            other => panic!("expected formula, got {other:?}"),
1275        }
1276        assert_eq!(
1277            wb.get_cell_value("Types", "F1").unwrap(),
1278            CellValue::Error("#N/A".to_string())
1279        );
1280        assert_eq!(wb.get_cell_value("Types", "G1").unwrap(), CellValue::Empty);
1281    }
1282
1283    #[test]
1284    fn test_apply_stream_optimized_save_reopen() {
1285        let dir = TempDir::new().unwrap();
1286        let path = dir.path().join("stream_optimized.xlsx");
1287
1288        let mut wb = Workbook::new();
1289        wb.set_cell_value("Sheet1", "A1", "Normal").unwrap();
1290
1291        let mut sw = wb.new_stream_writer("Fast").unwrap();
1292        sw.write_row(1, &[CellValue::from("Name"), CellValue::from("Value")])
1293            .unwrap();
1294        sw.write_row(2, &[CellValue::from("Alice"), CellValue::from(100)])
1295            .unwrap();
1296        sw.write_row(3, &[CellValue::from("Bob"), CellValue::from(200)])
1297            .unwrap();
1298        wb.apply_stream_writer(sw).unwrap();
1299
1300        wb.save(&path).unwrap();
1301
1302        let wb2 = Workbook::open(&path).unwrap();
1303        assert_eq!(wb2.sheet_names(), vec!["Sheet1", "Fast"]);
1304        assert_eq!(
1305            wb2.get_cell_value("Fast", "A1").unwrap(),
1306            CellValue::String("Name".to_string())
1307        );
1308        assert_eq!(
1309            wb2.get_cell_value("Fast", "B2").unwrap(),
1310            CellValue::Number(100.0)
1311        );
1312        assert_eq!(
1313            wb2.get_cell_value("Fast", "A3").unwrap(),
1314            CellValue::String("Bob".to_string())
1315        );
1316    }
1317}