logicaffeine_web/
learn_state.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum TabMode {
11 #[default]
12 Lesson,
13 Examples,
14 Practice,
15 Test,
16}
17
18impl TabMode {
19 pub fn label(&self) -> &'static str {
21 match self {
22 TabMode::Lesson => "LESSON",
23 TabMode::Examples => "EXAMPLES",
24 TabMode::Practice => "PRACTICE",
25 TabMode::Test => "TEST",
26 }
27 }
28
29 pub fn all() -> [TabMode; 4] {
31 [TabMode::Lesson, TabMode::Examples, TabMode::Practice, TabMode::Test]
32 }
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct ModuleTabState {
38 pub module_id: String,
39 pub current_tab: TabMode,
40 pub exercise_index: usize,
41 pub submitted: bool,
42}
43
44impl ModuleTabState {
45 pub fn new(module_id: &str) -> Self {
46 Self {
47 module_id: module_id.to_string(),
48 current_tab: TabMode::Lesson,
49 exercise_index: 0,
50 submitted: false,
51 }
52 }
53
54 pub fn switch_tab(&mut self, tab: TabMode) {
56 self.current_tab = tab;
57 self.exercise_index = 0;
58 self.submitted = false;
59 }
60
61 pub fn reset_exercise(&mut self) {
63 self.exercise_index = 0;
64 self.submitted = false;
65 }
66}
67
68#[derive(Debug, Clone, Default)]
70pub struct FocusState {
71 pub focused_era: Option<String>,
73 pub expanded_module: Option<(String, String)>, }
76
77impl FocusState {
78 pub fn new() -> Self {
79 Self::default()
80 }
81
82 pub fn focus_era(&mut self, era_id: &str) {
84 self.focused_era = Some(era_id.to_string());
85 }
86
87 pub fn expand_module(&mut self, era_id: &str, module_id: &str) {
89 self.focused_era = Some(era_id.to_string());
90 self.expanded_module = Some((era_id.to_string(), module_id.to_string()));
91 }
92
93 pub fn collapse_module(&mut self) {
95 self.expanded_module = None;
96 }
97
98 pub fn unfocus(&mut self) {
100 self.focused_era = None;
101 self.expanded_module = None;
102 }
103
104 pub fn is_era_visible(&self, era_id: &str) -> bool {
106 match &self.focused_era {
107 None => true,
108 Some(focused) => focused == era_id,
109 }
110 }
111
112 pub fn is_module_expanded(&self, era_id: &str, module_id: &str) -> bool {
114 match &self.expanded_module {
115 None => false,
116 Some((e, m)) => e == era_id && m == module_id,
117 }
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_tab_modes_all_four_exist() {
127 let tabs = TabMode::all();
128 assert_eq!(tabs.len(), 4);
129 assert_eq!(tabs[0], TabMode::Lesson);
130 assert_eq!(tabs[1], TabMode::Examples);
131 assert_eq!(tabs[2], TabMode::Practice);
132 assert_eq!(tabs[3], TabMode::Test);
133 }
134
135 #[test]
136 fn test_initial_tab_is_lesson() {
137 let state = ModuleTabState::new("test-module");
138 assert_eq!(state.current_tab, TabMode::Lesson);
139 }
140
141 #[test]
142 fn test_tab_switch_resets_exercise_index() {
143 let mut state = ModuleTabState::new("test-module");
144 state.exercise_index = 5;
145 state.submitted = true;
146
147 state.switch_tab(TabMode::Practice);
148
149 assert_eq!(state.current_tab, TabMode::Practice);
150 assert_eq!(state.exercise_index, 0);
151 assert!(!state.submitted);
152 }
153
154 #[test]
155 fn test_focus_state_toggles_era() {
156 let mut focus = FocusState::new();
157 assert!(focus.focused_era.is_none());
158
159 focus.focus_era("first-steps");
160 assert_eq!(focus.focused_era, Some("first-steps".to_string()));
161
162 focus.unfocus();
163 assert!(focus.focused_era.is_none());
164 }
165
166 #[test]
167 fn test_is_era_visible_when_focused() {
168 let mut focus = FocusState::new();
169
170 assert!(focus.is_era_visible("first-steps"));
172 assert!(focus.is_era_visible("mastery"));
173
174 focus.focus_era("first-steps");
176 assert!(focus.is_era_visible("first-steps"));
177 assert!(!focus.is_era_visible("mastery"));
178 }
179
180 #[test]
181 fn test_module_expansion() {
182 let mut focus = FocusState::new();
183
184 focus.expand_module("first-steps", "introduction");
185 assert!(focus.is_module_expanded("first-steps", "introduction"));
186 assert!(!focus.is_module_expanded("first-steps", "syllogistic"));
187
188 focus.collapse_module();
189 assert!(!focus.is_module_expanded("first-steps", "introduction"));
190 assert!(focus.is_era_visible("first-steps"));
192 }
193}