1use crate::error::{Error, Result};
7use sheetkit_xml::worksheet::{DataValidation, DataValidations, WorksheetXml};
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum ValidationType {
12 None,
14 Whole,
15 Decimal,
16 List,
17 Date,
18 Time,
19 TextLength,
20 Custom,
21}
22
23impl ValidationType {
24 pub fn as_str(&self) -> &str {
26 match self {
27 ValidationType::None => "none",
28 ValidationType::Whole => "whole",
29 ValidationType::Decimal => "decimal",
30 ValidationType::List => "list",
31 ValidationType::Date => "date",
32 ValidationType::Time => "time",
33 ValidationType::TextLength => "textLength",
34 ValidationType::Custom => "custom",
35 }
36 }
37
38 pub fn parse(s: &str) -> Option<Self> {
40 match s {
41 "none" => Some(ValidationType::None),
42 "whole" => Some(ValidationType::Whole),
43 "decimal" => Some(ValidationType::Decimal),
44 "list" => Some(ValidationType::List),
45 "date" => Some(ValidationType::Date),
46 "time" => Some(ValidationType::Time),
47 "textLength" => Some(ValidationType::TextLength),
48 "custom" => Some(ValidationType::Custom),
49 _ => None,
50 }
51 }
52
53 pub fn uses_operator(&self) -> bool {
55 matches!(
56 self,
57 ValidationType::Whole
58 | ValidationType::Decimal
59 | ValidationType::Date
60 | ValidationType::Time
61 | ValidationType::TextLength
62 )
63 }
64}
65
66#[derive(Debug, Clone, PartialEq)]
68pub enum ValidationOperator {
69 Between,
70 NotBetween,
71 Equal,
72 NotEqual,
73 LessThan,
74 LessThanOrEqual,
75 GreaterThan,
76 GreaterThanOrEqual,
77}
78
79impl ValidationOperator {
80 pub fn as_str(&self) -> &str {
82 match self {
83 ValidationOperator::Between => "between",
84 ValidationOperator::NotBetween => "notBetween",
85 ValidationOperator::Equal => "equal",
86 ValidationOperator::NotEqual => "notEqual",
87 ValidationOperator::LessThan => "lessThan",
88 ValidationOperator::LessThanOrEqual => "lessThanOrEqual",
89 ValidationOperator::GreaterThan => "greaterThan",
90 ValidationOperator::GreaterThanOrEqual => "greaterThanOrEqual",
91 }
92 }
93
94 pub fn parse(s: &str) -> Option<Self> {
96 match s {
97 "between" => Some(ValidationOperator::Between),
98 "notBetween" => Some(ValidationOperator::NotBetween),
99 "equal" => Some(ValidationOperator::Equal),
100 "notEqual" => Some(ValidationOperator::NotEqual),
101 "lessThan" => Some(ValidationOperator::LessThan),
102 "lessThanOrEqual" => Some(ValidationOperator::LessThanOrEqual),
103 "greaterThan" => Some(ValidationOperator::GreaterThan),
104 "greaterThanOrEqual" => Some(ValidationOperator::GreaterThanOrEqual),
105 _ => None,
106 }
107 }
108
109 pub fn needs_formula2(&self) -> bool {
111 matches!(
112 self,
113 ValidationOperator::Between | ValidationOperator::NotBetween
114 )
115 }
116}
117
118#[derive(Debug, Clone, PartialEq)]
120pub enum ErrorStyle {
121 Stop,
122 Warning,
123 Information,
124}
125
126impl ErrorStyle {
127 pub fn as_str(&self) -> &str {
129 match self {
130 ErrorStyle::Stop => "stop",
131 ErrorStyle::Warning => "warning",
132 ErrorStyle::Information => "information",
133 }
134 }
135
136 pub fn parse(s: &str) -> Option<Self> {
138 match s {
139 "stop" => Some(ErrorStyle::Stop),
140 "warning" => Some(ErrorStyle::Warning),
141 "information" => Some(ErrorStyle::Information),
142 _ => None,
143 }
144 }
145}
146
147#[derive(Debug, Clone)]
149pub struct DataValidationConfig {
150 pub sqref: String,
152 pub validation_type: ValidationType,
154 pub operator: Option<ValidationOperator>,
156 pub formula1: Option<String>,
158 pub formula2: Option<String>,
160 pub allow_blank: bool,
162 pub error_style: Option<ErrorStyle>,
164 pub error_title: Option<String>,
166 pub error_message: Option<String>,
168 pub prompt_title: Option<String>,
170 pub prompt_message: Option<String>,
172 pub show_input_message: bool,
174 pub show_error_message: bool,
176}
177
178impl DataValidationConfig {
179 pub fn dropdown(sqref: &str, items: &[&str]) -> Self {
184 let formula = format!("\"{}\"", items.join(","));
185 Self {
186 sqref: sqref.to_string(),
187 validation_type: ValidationType::List,
188 operator: None,
189 formula1: Some(formula),
190 formula2: None,
191 allow_blank: true,
192 error_style: Some(ErrorStyle::Stop),
193 error_title: None,
194 error_message: None,
195 prompt_title: None,
196 prompt_message: None,
197 show_input_message: true,
198 show_error_message: true,
199 }
200 }
201
202 pub fn whole_number(sqref: &str, min: i64, max: i64) -> Self {
204 Self {
205 sqref: sqref.to_string(),
206 validation_type: ValidationType::Whole,
207 operator: Some(ValidationOperator::Between),
208 formula1: Some(min.to_string()),
209 formula2: Some(max.to_string()),
210 allow_blank: true,
211 error_style: Some(ErrorStyle::Stop),
212 error_title: None,
213 error_message: None,
214 prompt_title: None,
215 prompt_message: None,
216 show_input_message: true,
217 show_error_message: true,
218 }
219 }
220
221 pub fn decimal(sqref: &str, min: f64, max: f64) -> Self {
223 Self {
224 sqref: sqref.to_string(),
225 validation_type: ValidationType::Decimal,
226 operator: Some(ValidationOperator::Between),
227 formula1: Some(min.to_string()),
228 formula2: Some(max.to_string()),
229 allow_blank: true,
230 error_style: Some(ErrorStyle::Stop),
231 error_title: None,
232 error_message: None,
233 prompt_title: None,
234 prompt_message: None,
235 show_input_message: true,
236 show_error_message: true,
237 }
238 }
239
240 pub fn text_length(sqref: &str, operator: ValidationOperator, length: u32) -> Self {
242 Self {
243 sqref: sqref.to_string(),
244 validation_type: ValidationType::TextLength,
245 operator: Some(operator),
246 formula1: Some(length.to_string()),
247 formula2: None,
248 allow_blank: true,
249 error_style: Some(ErrorStyle::Stop),
250 error_title: None,
251 error_message: None,
252 prompt_title: None,
253 prompt_message: None,
254 show_input_message: true,
255 show_error_message: true,
256 }
257 }
258}
259
260fn validate_sqref(sqref: &str) -> Result<()> {
266 if sqref.is_empty() {
267 return Err(Error::InvalidReference {
268 reference: sqref.to_string(),
269 });
270 }
271 for part in sqref.split(' ') {
273 if part.is_empty() {
274 return Err(Error::InvalidReference {
275 reference: sqref.to_string(),
276 });
277 }
278 for side in part.split(':') {
281 let has_alpha = side.chars().any(|c| c.is_ascii_alphabetic());
282 let has_digit = side.chars().any(|c| c.is_ascii_digit());
283 if !has_alpha || !has_digit {
284 return Err(Error::InvalidReference {
285 reference: sqref.to_string(),
286 });
287 }
288 }
289 }
290 Ok(())
291}
292
293fn validate_formulas(config: &DataValidationConfig) -> Result<()> {
295 match &config.validation_type {
296 ValidationType::None => {}
297 ValidationType::List | ValidationType::Custom => {
298 if config.formula1.as_ref().is_none_or(|f| f.is_empty()) {
299 return Err(Error::InvalidArgument(format!(
300 "formula1 is required for {:?} validation",
301 config.validation_type
302 )));
303 }
304 }
305 _ => {
306 if config.formula1.as_ref().is_none_or(|f| f.is_empty()) {
308 return Err(Error::InvalidArgument(format!(
309 "formula1 is required for {:?} validation",
310 config.validation_type
311 )));
312 }
313 if let Some(op) = &config.operator {
314 if op.needs_formula2() && config.formula2.as_ref().is_none_or(|f| f.is_empty()) {
315 return Err(Error::InvalidArgument(format!(
316 "formula2 is required for {:?} operator",
317 op
318 )));
319 }
320 }
321 }
322 }
323 Ok(())
324}
325
326pub fn config_to_xml(config: &DataValidationConfig) -> DataValidation {
328 DataValidation {
329 validation_type: Some(config.validation_type.as_str().to_string()),
330 operator: config.operator.as_ref().map(|o| o.as_str().to_string()),
331 allow_blank: if config.allow_blank { Some(true) } else { None },
332 show_drop_down: None,
333 show_input_message: if config.show_input_message {
334 Some(true)
335 } else {
336 None
337 },
338 show_error_message: if config.show_error_message {
339 Some(true)
340 } else {
341 None
342 },
343 error_style: config.error_style.as_ref().map(|e| e.as_str().to_string()),
344 ime_mode: None,
345 error_title: config.error_title.clone(),
346 error: config.error_message.clone(),
347 prompt_title: config.prompt_title.clone(),
348 prompt: config.prompt_message.clone(),
349 sqref: config.sqref.clone(),
350 formula1: config.formula1.clone(),
351 formula2: config.formula2.clone(),
352 }
353}
354
355fn xml_to_config(dv: &DataValidation) -> DataValidationConfig {
357 DataValidationConfig {
358 sqref: dv.sqref.clone(),
359 validation_type: dv
360 .validation_type
361 .as_deref()
362 .and_then(ValidationType::parse)
363 .unwrap_or(ValidationType::None),
364 operator: dv.operator.as_deref().and_then(ValidationOperator::parse),
365 formula1: dv.formula1.clone(),
366 formula2: dv.formula2.clone(),
367 allow_blank: dv.allow_blank.unwrap_or(false),
368 error_style: dv.error_style.as_deref().and_then(ErrorStyle::parse),
369 error_title: dv.error_title.clone(),
370 error_message: dv.error.clone(),
371 prompt_title: dv.prompt_title.clone(),
372 prompt_message: dv.prompt.clone(),
373 show_input_message: dv.show_input_message.unwrap_or(false),
374 show_error_message: dv.show_error_message.unwrap_or(false),
375 }
376}
377
378pub fn add_validation(ws: &mut WorksheetXml, config: &DataValidationConfig) -> Result<()> {
380 validate_sqref(&config.sqref)?;
381 validate_formulas(config)?;
382 let dv = config_to_xml(config);
383 let dvs = ws.data_validations.get_or_insert_with(|| DataValidations {
384 count: Some(0),
385 disable_prompts: None,
386 x_window: None,
387 y_window: None,
388 data_validations: Vec::new(),
389 });
390 dvs.data_validations.push(dv);
391 dvs.count = Some(dvs.data_validations.len() as u32);
392 Ok(())
393}
394
395pub fn get_validations(ws: &WorksheetXml) -> Vec<DataValidationConfig> {
397 match &ws.data_validations {
398 Some(dvs) => dvs.data_validations.iter().map(xml_to_config).collect(),
399 None => Vec::new(),
400 }
401}
402
403pub fn remove_validation(ws: &mut WorksheetXml, sqref: &str) -> Result<()> {
407 if let Some(ref mut dvs) = ws.data_validations {
408 dvs.data_validations.retain(|dv| dv.sqref != sqref);
409 dvs.count = Some(dvs.data_validations.len() as u32);
410 if dvs.data_validations.is_empty() {
411 ws.data_validations = None;
412 }
413 }
414 Ok(())
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_dropdown_validation() {
423 let config = DataValidationConfig::dropdown("A1:A100", &["Yes", "No", "Maybe"]);
424 assert_eq!(config.sqref, "A1:A100");
425 assert_eq!(config.validation_type, ValidationType::List);
426 assert_eq!(config.formula1, Some("\"Yes,No,Maybe\"".to_string()));
427 assert!(config.allow_blank);
428 assert!(config.show_input_message);
429 assert!(config.show_error_message);
430 }
431
432 #[test]
433 fn test_whole_number_validation() {
434 let config = DataValidationConfig::whole_number("B1:B50", 1, 100);
435 assert_eq!(config.sqref, "B1:B50");
436 assert_eq!(config.validation_type, ValidationType::Whole);
437 assert_eq!(config.operator, Some(ValidationOperator::Between));
438 assert_eq!(config.formula1, Some("1".to_string()));
439 assert_eq!(config.formula2, Some("100".to_string()));
440 }
441
442 #[test]
443 fn test_decimal_validation() {
444 let config = DataValidationConfig::decimal("C1:C10", 0.0, 99.99);
445 assert_eq!(config.sqref, "C1:C10");
446 assert_eq!(config.validation_type, ValidationType::Decimal);
447 assert_eq!(config.operator, Some(ValidationOperator::Between));
448 assert_eq!(config.formula1, Some("0".to_string()));
449 assert_eq!(config.formula2, Some("99.99".to_string()));
450 }
451
452 #[test]
453 fn test_text_length_validation() {
454 let config =
455 DataValidationConfig::text_length("D1:D10", ValidationOperator::LessThanOrEqual, 255);
456 assert_eq!(config.sqref, "D1:D10");
457 assert_eq!(config.validation_type, ValidationType::TextLength);
458 assert_eq!(config.operator, Some(ValidationOperator::LessThanOrEqual));
459 assert_eq!(config.formula1, Some("255".to_string()));
460 }
461
462 #[test]
463 fn test_config_to_xml_roundtrip() {
464 let config = DataValidationConfig::dropdown("A1:A10", &["Red", "Blue"]);
465 let xml = config_to_xml(&config);
466 assert_eq!(xml.validation_type, Some("list".to_string()));
467 assert_eq!(xml.sqref, "A1:A10");
468 assert_eq!(xml.formula1, Some("\"Red,Blue\"".to_string()));
469 assert_eq!(xml.allow_blank, Some(true));
470 assert_eq!(xml.show_input_message, Some(true));
471 assert_eq!(xml.show_error_message, Some(true));
472 }
473
474 #[test]
475 fn test_add_validation_to_worksheet() {
476 let mut ws = WorksheetXml::default();
477 let config = DataValidationConfig::dropdown("A1:A100", &["Yes", "No"]);
478 add_validation(&mut ws, &config).unwrap();
479
480 assert!(ws.data_validations.is_some());
481 let dvs = ws.data_validations.as_ref().unwrap();
482 assert_eq!(dvs.count, Some(1));
483 assert_eq!(dvs.data_validations.len(), 1);
484 assert_eq!(dvs.data_validations[0].sqref, "A1:A100");
485 }
486
487 #[test]
488 fn test_add_multiple_validations() {
489 let mut ws = WorksheetXml::default();
490 let config1 = DataValidationConfig::dropdown("A1:A100", &["Yes", "No"]);
491 let config2 = DataValidationConfig::whole_number("B1:B100", 1, 100);
492 add_validation(&mut ws, &config1).unwrap();
493 add_validation(&mut ws, &config2).unwrap();
494
495 let dvs = ws.data_validations.as_ref().unwrap();
496 assert_eq!(dvs.count, Some(2));
497 assert_eq!(dvs.data_validations.len(), 2);
498 }
499
500 #[test]
501 fn test_get_validations() {
502 let mut ws = WorksheetXml::default();
503 let config = DataValidationConfig::dropdown("A1:A100", &["Yes", "No"]);
504 add_validation(&mut ws, &config).unwrap();
505
506 let configs = get_validations(&ws);
507 assert_eq!(configs.len(), 1);
508 assert_eq!(configs[0].sqref, "A1:A100");
509 assert_eq!(configs[0].validation_type, ValidationType::List);
510 }
511
512 #[test]
513 fn test_get_validations_empty() {
514 let ws = WorksheetXml::default();
515 let configs = get_validations(&ws);
516 assert!(configs.is_empty());
517 }
518
519 #[test]
520 fn test_remove_validation() {
521 let mut ws = WorksheetXml::default();
522 let config1 = DataValidationConfig::dropdown("A1:A100", &["Yes", "No"]);
523 let config2 = DataValidationConfig::whole_number("B1:B100", 1, 100);
524 add_validation(&mut ws, &config1).unwrap();
525 add_validation(&mut ws, &config2).unwrap();
526
527 remove_validation(&mut ws, "A1:A100").unwrap();
528
529 let dvs = ws.data_validations.as_ref().unwrap();
530 assert_eq!(dvs.count, Some(1));
531 assert_eq!(dvs.data_validations.len(), 1);
532 assert_eq!(dvs.data_validations[0].sqref, "B1:B100");
533 }
534
535 #[test]
536 fn test_remove_last_validation_clears_container() {
537 let mut ws = WorksheetXml::default();
538 let config = DataValidationConfig::dropdown("A1:A100", &["Yes", "No"]);
539 add_validation(&mut ws, &config).unwrap();
540 remove_validation(&mut ws, "A1:A100").unwrap();
541
542 assert!(ws.data_validations.is_none());
543 }
544
545 #[test]
546 fn test_remove_nonexistent_validation() {
547 let mut ws = WorksheetXml::default();
548 remove_validation(&mut ws, "Z1:Z99").unwrap();
550 assert!(ws.data_validations.is_none());
551 }
552
553 #[test]
554 fn test_validation_xml_serialization_roundtrip() {
555 let mut ws = WorksheetXml::default();
556 let config = DataValidationConfig::dropdown("A1:A10", &["Apple", "Banana"]);
557 add_validation(&mut ws, &config).unwrap();
558
559 let xml = quick_xml::se::to_string(&ws).unwrap();
560 assert!(xml.contains("dataValidations"));
561 assert!(xml.contains("A1:A10"));
562
563 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
564 assert!(parsed.data_validations.is_some());
565 let dvs = parsed.data_validations.as_ref().unwrap();
566 assert_eq!(dvs.data_validations.len(), 1);
567 assert_eq!(dvs.data_validations[0].sqref, "A1:A10");
568 assert_eq!(
569 dvs.data_validations[0].validation_type,
570 Some("list".to_string())
571 );
572 }
573
574 #[test]
575 fn test_whole_number_validation_xml_roundtrip() {
576 let mut ws = WorksheetXml::default();
577 let config = DataValidationConfig::whole_number("B1:B50", 10, 200);
578 add_validation(&mut ws, &config).unwrap();
579
580 let xml = quick_xml::se::to_string(&ws).unwrap();
581 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
582
583 let configs = get_validations(&parsed);
584 assert_eq!(configs.len(), 1);
585 assert_eq!(configs[0].sqref, "B1:B50");
586 assert_eq!(configs[0].validation_type, ValidationType::Whole);
587 assert_eq!(configs[0].operator, Some(ValidationOperator::Between));
588 assert_eq!(configs[0].formula1, Some("10".to_string()));
589 assert_eq!(configs[0].formula2, Some("200".to_string()));
590 }
591
592 #[test]
593 fn test_decimal_validation_xml_roundtrip() {
594 let mut ws = WorksheetXml::default();
595 let config = DataValidationConfig::decimal("C1:C10", 1.5, 99.9);
596 add_validation(&mut ws, &config).unwrap();
597
598 let xml = quick_xml::se::to_string(&ws).unwrap();
599 let parsed: WorksheetXml = quick_xml::de::from_str(&xml).unwrap();
600
601 let configs = get_validations(&parsed);
602 assert_eq!(configs.len(), 1);
603 assert_eq!(configs[0].validation_type, ValidationType::Decimal);
604 }
605
606 #[test]
607 fn test_validation_type_as_str() {
608 assert_eq!(ValidationType::None.as_str(), "none");
609 assert_eq!(ValidationType::Whole.as_str(), "whole");
610 assert_eq!(ValidationType::Decimal.as_str(), "decimal");
611 assert_eq!(ValidationType::List.as_str(), "list");
612 assert_eq!(ValidationType::Date.as_str(), "date");
613 assert_eq!(ValidationType::Time.as_str(), "time");
614 assert_eq!(ValidationType::TextLength.as_str(), "textLength");
615 assert_eq!(ValidationType::Custom.as_str(), "custom");
616 }
617
618 #[test]
619 fn test_validation_operator_as_str() {
620 assert_eq!(ValidationOperator::Between.as_str(), "between");
621 assert_eq!(ValidationOperator::NotBetween.as_str(), "notBetween");
622 assert_eq!(ValidationOperator::Equal.as_str(), "equal");
623 assert_eq!(ValidationOperator::NotEqual.as_str(), "notEqual");
624 assert_eq!(ValidationOperator::LessThan.as_str(), "lessThan");
625 assert_eq!(
626 ValidationOperator::LessThanOrEqual.as_str(),
627 "lessThanOrEqual"
628 );
629 assert_eq!(ValidationOperator::GreaterThan.as_str(), "greaterThan");
630 assert_eq!(
631 ValidationOperator::GreaterThanOrEqual.as_str(),
632 "greaterThanOrEqual"
633 );
634 }
635
636 #[test]
637 fn test_error_style_as_str() {
638 assert_eq!(ErrorStyle::Stop.as_str(), "stop");
639 assert_eq!(ErrorStyle::Warning.as_str(), "warning");
640 assert_eq!(ErrorStyle::Information.as_str(), "information");
641 }
642
643 #[test]
644 fn test_none_type_roundtrip() {
645 assert_eq!(ValidationType::parse("none"), Some(ValidationType::None));
646 assert_eq!(ValidationType::None.as_str(), "none");
647 }
648
649 #[test]
650 fn test_unknown_type_defaults_to_none() {
651 let dv = DataValidation {
652 validation_type: Some("unknownFuture".to_string()),
653 operator: None,
654 allow_blank: None,
655 show_drop_down: None,
656 show_input_message: None,
657 show_error_message: None,
658 error_style: None,
659 ime_mode: None,
660 error_title: None,
661 error: None,
662 prompt_title: None,
663 prompt: None,
664 sqref: "A1".to_string(),
665 formula1: None,
666 formula2: None,
667 };
668 let config = xml_to_config(&dv);
669 assert_eq!(config.validation_type, ValidationType::None);
670 }
671
672 #[test]
673 fn test_validate_sqref_valid() {
674 assert!(validate_sqref("A1").is_ok());
675 assert!(validate_sqref("A1:B10").is_ok());
676 assert!(validate_sqref("A1:B10 D1:E10").is_ok());
677 assert!(validate_sqref("AA100:ZZ999").is_ok());
678 }
679
680 #[test]
681 fn test_validate_sqref_invalid() {
682 assert!(validate_sqref("").is_err());
683 assert!(validate_sqref("hello").is_err());
684 assert!(validate_sqref("123").is_err());
685 assert!(validate_sqref("A1: B10").is_err()); }
687
688 #[test]
689 fn test_add_validation_rejects_empty_sqref() {
690 let mut ws = WorksheetXml::default();
691 let config = DataValidationConfig {
692 sqref: "".to_string(),
693 validation_type: ValidationType::List,
694 operator: None,
695 formula1: Some("\"A,B\"".to_string()),
696 formula2: None,
697 allow_blank: false,
698 error_style: None,
699 error_title: None,
700 error_message: None,
701 prompt_title: None,
702 prompt_message: None,
703 show_input_message: false,
704 show_error_message: false,
705 };
706 assert!(add_validation(&mut ws, &config).is_err());
707 }
708
709 #[test]
710 fn test_add_validation_rejects_missing_formula1_for_list() {
711 let mut ws = WorksheetXml::default();
712 let config = DataValidationConfig {
713 sqref: "A1:A10".to_string(),
714 validation_type: ValidationType::List,
715 operator: None,
716 formula1: None,
717 formula2: None,
718 allow_blank: false,
719 error_style: None,
720 error_title: None,
721 error_message: None,
722 prompt_title: None,
723 prompt_message: None,
724 show_input_message: false,
725 show_error_message: false,
726 };
727 assert!(add_validation(&mut ws, &config).is_err());
728 }
729
730 #[test]
731 fn test_add_validation_rejects_missing_formula2_for_between() {
732 let mut ws = WorksheetXml::default();
733 let config = DataValidationConfig {
734 sqref: "A1:A10".to_string(),
735 validation_type: ValidationType::Whole,
736 operator: Some(ValidationOperator::Between),
737 formula1: Some("1".to_string()),
738 formula2: None,
739 allow_blank: false,
740 error_style: None,
741 error_title: None,
742 error_message: None,
743 prompt_title: None,
744 prompt_message: None,
745 show_input_message: false,
746 show_error_message: false,
747 };
748 assert!(add_validation(&mut ws, &config).is_err());
749 }
750
751 #[test]
752 fn test_none_type_no_formula_required() {
753 let mut ws = WorksheetXml::default();
754 let config = DataValidationConfig {
755 sqref: "A1:A10".to_string(),
756 validation_type: ValidationType::None,
757 operator: None,
758 formula1: None,
759 formula2: None,
760 allow_blank: false,
761 error_style: None,
762 error_title: None,
763 error_message: None,
764 prompt_title: Some("Hint".to_string()),
765 prompt_message: Some("Enter a value".to_string()),
766 show_input_message: true,
767 show_error_message: false,
768 };
769 assert!(add_validation(&mut ws, &config).is_ok());
770 let configs = get_validations(&ws);
771 assert_eq!(configs[0].validation_type, ValidationType::None);
772 }
773
774 #[test]
775 fn test_uses_operator() {
776 assert!(!ValidationType::None.uses_operator());
777 assert!(ValidationType::Whole.uses_operator());
778 assert!(ValidationType::Decimal.uses_operator());
779 assert!(!ValidationType::List.uses_operator());
780 assert!(ValidationType::Date.uses_operator());
781 assert!(ValidationType::Time.uses_operator());
782 assert!(ValidationType::TextLength.uses_operator());
783 assert!(!ValidationType::Custom.uses_operator());
784 }
785
786 #[test]
787 fn test_needs_formula2() {
788 assert!(ValidationOperator::Between.needs_formula2());
789 assert!(ValidationOperator::NotBetween.needs_formula2());
790 assert!(!ValidationOperator::Equal.needs_formula2());
791 assert!(!ValidationOperator::GreaterThan.needs_formula2());
792 }
793
794 #[test]
795 fn test_show_drop_down_preserved_in_xml() {
796 let dv = DataValidation {
797 validation_type: Some("list".to_string()),
798 operator: None,
799 allow_blank: None,
800 show_drop_down: Some(true),
801 show_input_message: None,
802 show_error_message: None,
803 error_style: None,
804 ime_mode: None,
805 error_title: None,
806 error: None,
807 prompt_title: None,
808 prompt: None,
809 sqref: "A1".to_string(),
810 formula1: Some("\"A,B\"".to_string()),
811 formula2: None,
812 };
813 let xml = quick_xml::se::to_string(&dv).unwrap();
814 assert!(xml.contains("showDropDown"));
815
816 let parsed: DataValidation = quick_xml::de::from_str(&xml).unwrap();
817 assert_eq!(parsed.show_drop_down, Some(true));
818 }
819
820 #[test]
821 fn test_ime_mode_preserved_in_xml() {
822 let dv = DataValidation {
823 validation_type: Some("whole".to_string()),
824 operator: None,
825 allow_blank: None,
826 show_drop_down: None,
827 show_input_message: None,
828 show_error_message: None,
829 error_style: None,
830 ime_mode: Some("hiragana".to_string()),
831 error_title: None,
832 error: None,
833 prompt_title: None,
834 prompt: None,
835 sqref: "A1".to_string(),
836 formula1: Some("1".to_string()),
837 formula2: None,
838 };
839 let xml = quick_xml::se::to_string(&dv).unwrap();
840 assert!(xml.contains("imeMode"));
841
842 let parsed: DataValidation = quick_xml::de::from_str(&xml).unwrap();
843 assert_eq!(parsed.ime_mode, Some("hiragana".to_string()));
844 }
845
846 #[test]
847 fn test_container_attrs_preserved_in_xml() {
848 let dvs = DataValidations {
849 count: Some(0),
850 disable_prompts: Some(true),
851 x_window: Some(100),
852 y_window: Some(200),
853 data_validations: Vec::new(),
854 };
855 let xml = quick_xml::se::to_string(&dvs).unwrap();
856 assert!(xml.contains("disablePrompts"));
857 assert!(xml.contains("xWindow"));
858 assert!(xml.contains("yWindow"));
859
860 let parsed: DataValidations = quick_xml::de::from_str(&xml).unwrap();
861 assert_eq!(parsed.disable_prompts, Some(true));
862 assert_eq!(parsed.x_window, Some(100));
863 assert_eq!(parsed.y_window, Some(200));
864 }
865}