aprender_test_showcase/core/
history.rs1use serde::{Deserialize, Serialize};
6use std::collections::VecDeque;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct HistoryEntry {
11 pub expression: String,
13 pub result: f64,
15 pub timestamp: u64,
17}
18
19impl HistoryEntry {
20 #[must_use]
22 pub fn new(expression: String, result: f64) -> Self {
23 Self {
24 expression,
25 result,
26 timestamp: Self::current_timestamp(),
27 }
28 }
29
30 #[must_use]
32 pub fn with_timestamp(expression: String, result: f64, timestamp: u64) -> Self {
33 Self {
34 expression,
35 result,
36 timestamp,
37 }
38 }
39
40 fn current_timestamp() -> u64 {
42 use std::time::{SystemTime, UNIX_EPOCH};
43 SystemTime::now()
44 .duration_since(UNIX_EPOCH)
45 .map(|d| d.as_millis() as u64)
46 .unwrap_or(0)
47 }
48
49 #[must_use]
51 pub fn display(&self) -> String {
52 format!("{} = {}", self.expression, self.result)
53 }
54}
55
56#[derive(Debug, Clone)]
61pub struct History {
62 entries: VecDeque<HistoryEntry>,
64 max_entries: usize,
66}
67
68impl Default for History {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl History {
75 pub const DEFAULT_MAX_ENTRIES: usize = 100;
77
78 #[must_use]
80 pub fn new() -> Self {
81 Self {
82 entries: VecDeque::new(),
83 max_entries: Self::DEFAULT_MAX_ENTRIES,
84 }
85 }
86
87 #[must_use]
89 pub fn with_capacity(max_entries: usize) -> Self {
90 Self {
91 entries: VecDeque::with_capacity(max_entries),
92 max_entries,
93 }
94 }
95
96 pub fn push(&mut self, entry: HistoryEntry) {
98 if self.entries.len() >= self.max_entries {
99 self.entries.pop_front();
100 }
101 self.entries.push_back(entry);
102 }
103
104 pub fn record(&mut self, expression: &str, result: f64) {
106 let entry = HistoryEntry::new(expression.to_string(), result);
107 self.push(entry);
108 }
109
110 #[must_use]
112 pub fn len(&self) -> usize {
113 self.entries.len()
114 }
115
116 #[must_use]
118 pub fn is_empty(&self) -> bool {
119 self.entries.is_empty()
120 }
121
122 #[must_use]
124 pub fn max_entries(&self) -> usize {
125 self.max_entries
126 }
127
128 pub fn clear(&mut self) {
130 self.entries.clear();
131 }
132
133 pub fn iter(&self) -> impl Iterator<Item = &HistoryEntry> {
135 self.entries.iter()
136 }
137
138 pub fn iter_rev(&self) -> impl Iterator<Item = &HistoryEntry> {
140 self.entries.iter().rev()
141 }
142
143 #[must_use]
145 pub fn last(&self) -> Option<&HistoryEntry> {
146 self.entries.back()
147 }
148
149 #[must_use]
151 pub fn first(&self) -> Option<&HistoryEntry> {
152 self.entries.front()
153 }
154
155 #[must_use]
157 pub fn get(&self, index: usize) -> Option<&HistoryEntry> {
158 self.entries.get(index)
159 }
160
161 #[must_use]
163 pub fn last_n(&self, n: usize) -> Vec<&HistoryEntry> {
164 self.entries.iter().rev().take(n).collect()
165 }
166
167 pub fn to_json(&self) -> Result<String, serde_json::Error> {
169 serde_json::to_string(&self.entries.iter().collect::<Vec<_>>())
170 }
171
172 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
174 let entries: Vec<HistoryEntry> = serde_json::from_str(json)?;
175 let mut history = Self::new();
176 for entry in entries {
177 history.push(entry);
178 }
179 Ok(history)
180 }
181
182 #[must_use]
184 pub fn export_formatted(&self) -> String {
185 self.entries
186 .iter()
187 .map(HistoryEntry::display)
188 .collect::<Vec<_>>()
189 .join("\n")
190 }
191}
192
193#[cfg(test)]
194#[allow(clippy::unwrap_used, clippy::float_cmp)]
195mod tests {
196 use super::*;
197
198 #[test]
201 fn test_history_entry_new() {
202 let entry = HistoryEntry::new("2 + 2".into(), 4.0);
203 assert_eq!(entry.expression, "2 + 2");
204 assert_eq!(entry.result, 4.0);
205 assert!(entry.timestamp > 0);
206 }
207
208 #[test]
209 fn test_history_entry_with_timestamp() {
210 let entry = HistoryEntry::with_timestamp("3 * 3".into(), 9.0, 1234567890);
211 assert_eq!(entry.expression, "3 * 3");
212 assert_eq!(entry.result, 9.0);
213 assert_eq!(entry.timestamp, 1234567890);
214 }
215
216 #[test]
217 fn test_history_entry_display() {
218 let entry = HistoryEntry::new("5 + 3".into(), 8.0);
219 assert_eq!(entry.display(), "5 + 3 = 8");
220 }
221
222 #[test]
223 fn test_history_entry_clone() {
224 let entry = HistoryEntry::new("1 + 1".into(), 2.0);
225 let cloned = entry.clone();
226 assert_eq!(entry, cloned);
227 }
228
229 #[test]
230 fn test_history_entry_serialize() {
231 let entry = HistoryEntry::with_timestamp("2 ^ 3".into(), 8.0, 1000);
232 let json = serde_json::to_string(&entry).unwrap();
233 assert!(json.contains("\"expression\":\"2 ^ 3\""));
234 assert!(json.contains("\"result\":8.0"));
235 }
236
237 #[test]
238 fn test_history_entry_deserialize() {
239 let json = r#"{"expression":"10 / 2","result":5.0,"timestamp":2000}"#;
240 let entry: HistoryEntry = serde_json::from_str(json).unwrap();
241 assert_eq!(entry.expression, "10 / 2");
242 assert_eq!(entry.result, 5.0);
243 assert_eq!(entry.timestamp, 2000);
244 }
245
246 #[test]
249 fn test_history_new() {
250 let history = History::new();
251 assert!(history.is_empty());
252 assert_eq!(history.len(), 0);
253 assert_eq!(history.max_entries(), History::DEFAULT_MAX_ENTRIES);
254 }
255
256 #[test]
257 fn test_history_default() {
258 let history = History::default();
259 assert!(history.is_empty());
260 }
261
262 #[test]
263 fn test_history_with_capacity() {
264 let history = History::with_capacity(50);
265 assert_eq!(history.max_entries(), 50);
266 }
267
268 #[test]
269 fn test_history_push() {
270 let mut history = History::new();
271 let entry = HistoryEntry::new("1 + 1".into(), 2.0);
272 history.push(entry);
273 assert_eq!(history.len(), 1);
274 }
275
276 #[test]
277 fn test_history_record() {
278 let mut history = History::new();
279 history.record("3 + 4", 7.0);
280 assert_eq!(history.len(), 1);
281 assert_eq!(history.last().unwrap().expression, "3 + 4");
282 assert_eq!(history.last().unwrap().result, 7.0);
283 }
284
285 #[test]
286 fn test_history_max_entries_enforcement() {
287 let mut history = History::with_capacity(3);
288 history.record("1", 1.0);
289 history.record("2", 2.0);
290 history.record("3", 3.0);
291 history.record("4", 4.0);
292
293 assert_eq!(history.len(), 3);
294 assert_eq!(history.first().unwrap().result, 2.0);
296 assert_eq!(history.last().unwrap().result, 4.0);
297 }
298
299 #[test]
300 fn test_history_clear() {
301 let mut history = History::new();
302 history.record("1", 1.0);
303 history.record("2", 2.0);
304 history.clear();
305 assert!(history.is_empty());
306 }
307
308 #[test]
309 fn test_history_iter() {
310 let mut history = History::new();
311 history.record("a", 1.0);
312 history.record("b", 2.0);
313 history.record("c", 3.0);
314
315 let results: Vec<f64> = history.iter().map(|e| e.result).collect();
316 assert_eq!(results, vec![1.0, 2.0, 3.0]);
317 }
318
319 #[test]
320 fn test_history_iter_rev() {
321 let mut history = History::new();
322 history.record("a", 1.0);
323 history.record("b", 2.0);
324 history.record("c", 3.0);
325
326 let results: Vec<f64> = history.iter_rev().map(|e| e.result).collect();
327 assert_eq!(results, vec![3.0, 2.0, 1.0]);
328 }
329
330 #[test]
331 fn test_history_last() {
332 let mut history = History::new();
333 assert!(history.last().is_none());
334
335 history.record("x", 10.0);
336 assert_eq!(history.last().unwrap().result, 10.0);
337
338 history.record("y", 20.0);
339 assert_eq!(history.last().unwrap().result, 20.0);
340 }
341
342 #[test]
343 fn test_history_first() {
344 let mut history = History::new();
345 assert!(history.first().is_none());
346
347 history.record("x", 10.0);
348 assert_eq!(history.first().unwrap().result, 10.0);
349
350 history.record("y", 20.0);
351 assert_eq!(history.first().unwrap().result, 10.0);
352 }
353
354 #[test]
355 fn test_history_get() {
356 let mut history = History::new();
357 history.record("a", 1.0);
358 history.record("b", 2.0);
359 history.record("c", 3.0);
360
361 assert_eq!(history.get(0).unwrap().result, 1.0);
362 assert_eq!(history.get(1).unwrap().result, 2.0);
363 assert_eq!(history.get(2).unwrap().result, 3.0);
364 assert!(history.get(3).is_none());
365 }
366
367 #[test]
368 fn test_history_last_n() {
369 let mut history = History::new();
370 history.record("a", 1.0);
371 history.record("b", 2.0);
372 history.record("c", 3.0);
373 history.record("d", 4.0);
374
375 let last_2: Vec<f64> = history.last_n(2).iter().map(|e| e.result).collect();
376 assert_eq!(last_2, vec![4.0, 3.0]);
377
378 let last_10 = history.last_n(10);
379 assert_eq!(last_10.len(), 4);
380 }
381
382 #[test]
383 fn test_history_to_json() {
384 let mut history = History::new();
385 history.push(HistoryEntry::with_timestamp("1+1".into(), 2.0, 1000));
386 history.push(HistoryEntry::with_timestamp("2+2".into(), 4.0, 2000));
387
388 let json = history.to_json().unwrap();
389 assert!(json.contains("1+1"));
390 assert!(json.contains("2+2"));
391 }
392
393 #[test]
394 fn test_history_from_json() {
395 let json = r#"[
396 {"expression":"a","result":1.0,"timestamp":1000},
397 {"expression":"b","result":2.0,"timestamp":2000}
398 ]"#;
399
400 let history = History::from_json(json).unwrap();
401 assert_eq!(history.len(), 2);
402 assert_eq!(history.first().unwrap().expression, "a");
403 assert_eq!(history.last().unwrap().expression, "b");
404 }
405
406 #[test]
407 fn test_history_from_json_invalid() {
408 let result = History::from_json("invalid json");
409 assert!(result.is_err());
410 }
411
412 #[test]
413 fn test_history_export_formatted() {
414 let mut history = History::new();
415 history.push(HistoryEntry::with_timestamp("1+1".into(), 2.0, 1000));
416 history.push(HistoryEntry::with_timestamp("2*3".into(), 6.0, 2000));
417
418 let formatted = history.export_formatted();
419 assert_eq!(formatted, "1+1 = 2\n2*3 = 6");
420 }
421
422 #[test]
423 fn test_history_export_formatted_empty() {
424 let history = History::new();
425 let formatted = history.export_formatted();
426 assert_eq!(formatted, "");
427 }
428
429 #[test]
430 fn test_history_clone() {
431 let mut history = History::new();
432 history.record("test", 42.0);
433
434 let cloned = history.clone();
435 assert_eq!(cloned.len(), 1);
436 assert_eq!(cloned.last().unwrap().result, 42.0);
437 }
438
439 #[test]
440 fn test_history_round_trip_json() {
441 let mut original = History::new();
442 original.push(HistoryEntry::with_timestamp("x".into(), 10.0, 100));
443 original.push(HistoryEntry::with_timestamp("y".into(), 20.0, 200));
444
445 let json = original.to_json().unwrap();
446 let restored = History::from_json(&json).unwrap();
447
448 assert_eq!(original.len(), restored.len());
449 for (orig, rest) in original.iter().zip(restored.iter()) {
450 assert_eq!(orig, rest);
451 }
452 }
453}