Skip to main content

kando_core/
config.rs

1use serde::{Deserialize, Serialize};
2
3use crate::board::storage::{BoardSection, ColumnConfig};
4
5#[derive(Debug, Serialize, Deserialize)]
6pub struct BoardConfig {
7    pub board: BoardSection,
8    #[serde(rename = "columns")]
9    pub columns: Vec<ColumnConfig>,
10}
11
12/// Marker file `.kando.toml` for git-synced boards.
13/// Committed to the repo; the only footprint in the working tree.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct KandoToml {
16    #[serde(default = "default_kando_branch")]
17    pub branch: String,
18}
19
20fn default_kando_branch() -> String {
21    "kando".to_string()
22}
23
24/// Per-user local preferences stored in `.kando/local.toml` (gitignored).
25/// Defaults to all-off so missing fields are handled gracefully.
26#[derive(Debug, Default, Serialize, Deserialize)]
27pub struct LocalConfig {
28    /// Whether the first-launch help hint has been shown.
29    #[serde(default, alias = "tutorial_shown")]
30    pub help_hint_shown: bool,
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    #[test]
38    fn local_config_default_creates_empty_struct() {
39        let _cfg = LocalConfig::default();
40    }
41
42    #[test]
43    fn local_config_toml_roundtrip() {
44        let original = LocalConfig::default();
45        let serialized = toml::to_string_pretty(&original).unwrap();
46        let _loaded: LocalConfig = toml::from_str(&serialized).unwrap();
47    }
48
49    #[test]
50    fn local_config_empty_string_deserializes() {
51        let _loaded: LocalConfig = toml::from_str("").unwrap();
52    }
53
54    #[test]
55    fn local_config_ignores_unknown_fields() {
56        // Old local.toml files may still contain `focus_mode` from before it was removed.
57        let _loaded: LocalConfig = toml::from_str("focus_mode = true\n").unwrap();
58    }
59
60    #[test]
61    fn local_config_default_help_hint_shown_is_false() {
62        assert!(!LocalConfig::default().help_hint_shown);
63    }
64
65    #[test]
66    fn local_config_help_hint_shown_true_roundtrip() {
67        let original = LocalConfig { help_hint_shown: true };
68        let serialized = toml::to_string_pretty(&original).unwrap();
69        let loaded: LocalConfig = toml::from_str(&serialized).unwrap();
70        assert!(loaded.help_hint_shown);
71    }
72
73    #[test]
74    fn local_config_missing_help_hint_shown_defaults_to_false() {
75        let loaded: LocalConfig = toml::from_str("").unwrap();
76        assert!(!loaded.help_hint_shown);
77    }
78
79    #[test]
80    fn local_config_legacy_tutorial_shown_deserializes_as_help_hint_shown() {
81        let loaded: LocalConfig = toml::from_str("tutorial_shown = true\n").unwrap();
82        assert!(loaded.help_hint_shown);
83    }
84
85    #[test]
86    fn local_config_serializes_with_new_field_name_not_alias() {
87        let cfg = LocalConfig { help_hint_shown: true };
88        let serialized = toml::to_string_pretty(&cfg).unwrap();
89        assert!(serialized.contains("help_hint_shown"));
90        assert!(!serialized.contains("tutorial_shown"));
91    }
92
93    #[test]
94    fn local_config_both_keys_present_is_rejected() {
95        let result = toml::from_str::<LocalConfig>(
96            "tutorial_shown = false\nhelp_hint_shown = true\n",
97        );
98        assert!(result.is_err(), "duplicate field must be rejected");
99    }
100
101    // ---- KandoToml tests ----
102
103    #[test]
104    fn kando_toml_roundtrip() {
105        let original = KandoToml { branch: "kando".to_string() };
106        let serialized = toml::to_string_pretty(&original).unwrap();
107        let loaded: KandoToml = toml::from_str(&serialized).unwrap();
108        assert_eq!(loaded.branch, "kando");
109    }
110
111    #[test]
112    fn kando_toml_default_branch_when_missing() {
113        let loaded: KandoToml = toml::from_str("").unwrap();
114        assert_eq!(loaded.branch, "kando");
115    }
116
117    #[test]
118    fn kando_toml_custom_branch() {
119        let loaded: KandoToml = toml::from_str("branch = \"my-board\"\n").unwrap();
120        assert_eq!(loaded.branch, "my-board");
121    }
122
123    #[test]
124    fn kando_toml_malformed_returns_err() {
125        let result = toml::from_str::<KandoToml>("branch = !!!");
126        assert!(result.is_err());
127    }
128
129    #[test]
130    fn kando_toml_ignores_unknown_fields() {
131        let loaded: KandoToml = toml::from_str("branch = \"kando\"\nextra = 42\n").unwrap();
132        assert_eq!(loaded.branch, "kando");
133    }
134
135    #[test]
136    fn kando_toml_serialization_contains_branch() {
137        let toml = KandoToml { branch: "kando".to_string() };
138        let s = toml::to_string_pretty(&toml).unwrap();
139        assert!(s.contains("branch = \"kando\""));
140    }
141
142    #[test]
143    fn kando_toml_branch_with_slashes() {
144        let loaded: KandoToml = toml::from_str("branch = \"team/board\"\n").unwrap();
145        assert_eq!(loaded.branch, "team/board");
146    }
147}