1use std::collections::HashMap;
2
3use serde_json::Value;
4
5pub trait Dirty {
7 fn changed(&self) -> bool;
9
10 fn changes(&self) -> HashMap<String, [Value; 2]>;
12
13 fn changed_attributes(&self) -> Vec<String>;
15
16 fn previous_changes(&self) -> &HashMap<String, [Value; 2]>;
18
19 fn attribute_changed(&self, name: &str) -> bool;
21
22 fn attribute_was(&self, name: &str) -> Option<Value>;
24
25 fn restore_attributes(&mut self);
27
28 fn clear_changes(&mut self);
30
31 fn changes_applied(&mut self);
33}
34
35#[derive(Debug, Clone, Default, PartialEq)]
37pub struct ChangeTracker {
38 original_values: HashMap<String, Value>,
39 changes: HashMap<String, [Value; 2]>,
40 previous_changes: HashMap<String, [Value; 2]>,
41}
42
43impl ChangeTracker {
44 #[must_use]
46 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn track_change(&mut self, name: &str, old: Value, new: Value) {
56 let key = name.to_owned();
57
58 if let Some(original) = self.original_values.get(&key).cloned() {
59 if new == original {
60 self.original_values.remove(&key);
61 self.changes.remove(&key);
62 } else {
63 self.changes.insert(key, [original, new]);
64 }
65 return;
66 }
67
68 if old == new {
69 return;
70 }
71
72 self.original_values.insert(key.clone(), old.clone());
73 self.changes.insert(key, [old, new]);
74 }
75
76 #[must_use]
78 pub fn changed(&self) -> bool {
79 !self.changes.is_empty()
80 }
81
82 #[must_use]
84 pub fn changes(&self) -> &HashMap<String, [Value; 2]> {
85 &self.changes
86 }
87
88 #[must_use]
90 pub fn previous_changes(&self) -> &HashMap<String, [Value; 2]> {
91 &self.previous_changes
92 }
93
94 #[must_use]
96 pub fn attribute_changed(&self, name: &str) -> bool {
97 self.changes.contains_key(name)
98 }
99
100 #[must_use]
102 pub fn attribute_was(&self, name: &str) -> Option<&Value> {
103 self.original_values.get(name)
104 }
105
106 pub fn restore_attribute(&mut self, name: &str) -> Option<Value> {
111 self.changes.remove(name);
112 self.original_values.remove(name)
113 }
114
115 pub fn clear(&mut self) {
117 self.original_values.clear();
118 self.changes.clear();
119 self.previous_changes.clear();
120 }
121
122 pub fn apply(&mut self) {
124 self.previous_changes = self.changes.clone();
125 self.original_values.clear();
126 self.changes.clear();
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use std::collections::HashMap;
133
134 use serde_json::{Value, json};
135
136 use super::{ChangeTracker, Dirty};
137
138 #[derive(Debug, Clone, PartialEq)]
139 struct TestProfile {
140 name: String,
141 age: i32,
142 tracker: ChangeTracker,
143 }
144
145 impl TestProfile {
146 fn new(name: &str, age: i32) -> Self {
147 Self {
148 name: name.to_owned(),
149 age,
150 tracker: ChangeTracker::new(),
151 }
152 }
153
154 fn set_name(&mut self, new_name: &str) {
155 let old = Value::String(self.name.clone());
156 let new = Value::String(new_name.to_owned());
157 self.tracker.track_change("name", old, new.clone());
158 self.name = new_name.to_owned();
159 }
160
161 fn set_age(&mut self, new_age: i32) {
162 let old = json!(self.age);
163 let new = json!(new_age);
164 self.tracker.track_change("age", old, new.clone());
165 self.age = new_age;
166 }
167
168 fn apply_value(&mut self, name: &str, value: Value) {
169 match name {
170 "name" => {
171 if let Value::String(text) = value {
172 self.name = text;
173 }
174 }
175 "age" => {
176 if let Some(number) = value.as_i64().and_then(|entry| i32::try_from(entry).ok())
177 {
178 self.age = number;
179 }
180 }
181 _ => {}
182 }
183 }
184 }
185
186 impl Dirty for TestProfile {
187 fn changed(&self) -> bool {
188 self.tracker.changed()
189 }
190
191 fn changes(&self) -> HashMap<String, [Value; 2]> {
192 self.tracker.changes().clone()
193 }
194
195 fn changed_attributes(&self) -> Vec<String> {
196 self.tracker.changes().keys().cloned().collect()
197 }
198
199 fn previous_changes(&self) -> &HashMap<String, [Value; 2]> {
200 self.tracker.previous_changes()
201 }
202
203 fn attribute_changed(&self, name: &str) -> bool {
204 self.tracker.attribute_changed(name)
205 }
206
207 fn attribute_was(&self, name: &str) -> Option<Value> {
208 self.tracker.attribute_was(name).cloned()
209 }
210
211 fn restore_attributes(&mut self) {
212 let names = self.changed_attributes();
213 for name in names {
214 if let Some(original) = self.tracker.restore_attribute(&name) {
215 self.apply_value(&name, original);
216 }
217 }
218 }
219
220 fn clear_changes(&mut self) {
221 self.tracker.clear();
222 }
223
224 fn changes_applied(&mut self) {
225 self.tracker.apply();
226 }
227 }
228
229 #[test]
230 fn track_single_change_records_original_and_new_values() {
231 let mut tracker = ChangeTracker::new();
232 tracker.track_change("name", json!("Alice"), json!("Bob"));
233
234 assert!(tracker.changed());
235 assert_eq!(
236 tracker.changes().get("name"),
237 Some(&[json!("Alice"), json!("Bob")])
238 );
239 assert_eq!(tracker.attribute_was("name"), Some(&json!("Alice")));
240 }
241
242 #[test]
243 fn track_multiple_changes_preserves_the_first_original_value() {
244 let mut tracker = ChangeTracker::new();
245 tracker.track_change("name", json!("Alice"), json!("Bob"));
246 tracker.track_change("name", json!("Bob"), json!("Carol"));
247
248 assert_eq!(
249 tracker.changes().get("name"),
250 Some(&[json!("Alice"), json!("Carol")])
251 );
252 assert_eq!(tracker.attribute_was("name"), Some(&json!("Alice")));
253 }
254
255 #[test]
256 fn changing_back_to_original_removes_the_change() {
257 let mut tracker = ChangeTracker::new();
258 tracker.track_change("name", json!("Alice"), json!("Bob"));
259 tracker.track_change("name", json!("Bob"), json!("Alice"));
260
261 assert!(!tracker.changed());
262 assert!(!tracker.attribute_changed("name"));
263 assert_eq!(tracker.attribute_was("name"), None);
264 assert!(tracker.changes().is_empty());
265 }
266
267 #[test]
268 fn restore_attribute_returns_original_value_and_clears_one_change() {
269 let mut tracker = ChangeTracker::new();
270 tracker.track_change("name", json!("Alice"), json!("Bob"));
271 tracker.track_change("age", json!(30), json!(31));
272
273 let restored = tracker.restore_attribute("name");
274
275 assert_eq!(restored, Some(json!("Alice")));
276 assert!(!tracker.attribute_changed("name"));
277 assert!(tracker.attribute_changed("age"));
278 }
279
280 #[test]
281 fn apply_moves_current_changes_into_previous_changes() {
282 let mut tracker = ChangeTracker::new();
283 tracker.track_change("name", json!("Alice"), json!("Bob"));
284 tracker.track_change("age", json!(30), json!(31));
285
286 tracker.apply();
287
288 assert!(!tracker.changed());
289 assert!(tracker.changes().is_empty());
290 assert_eq!(
291 tracker.previous_changes().get("name"),
292 Some(&[json!("Alice"), json!("Bob")])
293 );
294 assert_eq!(
295 tracker.previous_changes().get("age"),
296 Some(&[json!(30), json!(31)])
297 );
298 }
299
300 #[test]
301 fn apply_resets_originals_for_future_change_tracking() {
302 let mut tracker = ChangeTracker::new();
303 tracker.track_change("name", json!("Alice"), json!("Bob"));
304 tracker.apply();
305 tracker.track_change("name", json!("Bob"), json!("Carol"));
306
307 assert_eq!(tracker.attribute_was("name"), Some(&json!("Bob")));
308 assert_eq!(
309 tracker.changes().get("name"),
310 Some(&[json!("Bob"), json!("Carol")])
311 );
312 }
313
314 #[test]
315 fn clear_resets_current_and_previous_tracking() {
316 let mut tracker = ChangeTracker::new();
317 tracker.track_change("name", json!("Alice"), json!("Bob"));
318 tracker.apply();
319
320 tracker.clear();
321
322 assert!(!tracker.changed());
323 assert!(tracker.changes().is_empty());
324 assert!(tracker.previous_changes().is_empty());
325 }
326
327 #[test]
328 fn dirty_trait_reports_changes_and_original_values() {
329 let mut profile = TestProfile::new("Alice", 30);
330 profile.set_name("Bob");
331 profile.set_age(31);
332
333 let changes = profile.changes();
334
335 assert!(profile.changed());
336 assert!(profile.attribute_changed("name"));
337 assert_eq!(profile.attribute_was("name"), Some(json!("Alice")));
338 assert_eq!(changes.get("name"), Some(&[json!("Alice"), json!("Bob")]));
339 assert_eq!(changes.get("age"), Some(&[json!(30), json!(31)]));
340 }
341
342 #[test]
343 fn dirty_trait_restore_attributes_reverts_all_values() {
344 let mut profile = TestProfile::new("Alice", 30);
345 profile.set_name("Bob");
346 profile.set_age(31);
347
348 profile.restore_attributes();
349
350 assert_eq!(profile.name, "Alice");
351 assert_eq!(profile.age, 30);
352 assert!(!profile.changed());
353 assert!(profile.changes().is_empty());
354 }
355
356 #[test]
357 fn dirty_trait_changes_applied_exposes_previous_changes() {
358 let mut profile = TestProfile::new("Alice", 30);
359 profile.set_name("Bob");
360
361 profile.changes_applied();
362
363 assert!(!profile.changed());
364 assert_eq!(
365 profile.previous_changes().get("name"),
366 Some(&[json!("Alice"), json!("Bob")])
367 );
368 }
369
370 #[test]
371 fn dirty_trait_clear_changes_drops_history() {
372 let mut profile = TestProfile::new("Alice", 30);
373 profile.set_name("Bob");
374 profile.changes_applied();
375
376 profile.clear_changes();
377
378 assert!(profile.previous_changes().is_empty());
379 assert!(!profile.changed());
380 }
381 #[test]
382 fn track_change_ignores_equal_values() {
383 let mut tracker = ChangeTracker::new();
384 tracker.track_change("name", json!("Alice"), json!("Alice"));
385
386 assert!(!tracker.changed());
387 assert!(tracker.changes().is_empty());
388 }
389
390 #[test]
391 fn restore_attribute_returns_none_for_unknown_name() {
392 let mut tracker = ChangeTracker::new();
393 tracker.track_change("name", json!("Alice"), json!("Bob"));
394
395 assert_eq!(tracker.restore_attribute("email"), None);
396 assert!(tracker.attribute_changed("name"));
397 }
398
399 #[test]
400 fn attribute_was_returns_none_for_unknown_attribute() {
401 let tracker = ChangeTracker::new();
402 assert_eq!(tracker.attribute_was("name"), None);
403 }
404
405 #[test]
406 fn apply_without_changes_keeps_previous_changes_empty() {
407 let mut tracker = ChangeTracker::new();
408 tracker.apply();
409
410 assert!(!tracker.changed());
411 assert!(tracker.previous_changes().is_empty());
412 }
413
414 #[test]
415 fn dirty_trait_changed_attributes_lists_each_change_once() {
416 let mut profile = TestProfile::new("Alice", 30);
417 profile.set_name("Bob");
418 profile.set_age(31);
419
420 let mut changed = profile.changed_attributes();
421 changed.sort();
422
423 assert_eq!(changed, vec!["age".to_owned(), "name".to_owned()]);
424 }
425
426 #[test]
427 fn restore_attributes_after_apply_is_a_noop() {
428 let mut profile = TestProfile::new("Alice", 30);
429 profile.set_name("Bob");
430 profile.changes_applied();
431 profile.restore_attributes();
432
433 assert_eq!(profile.name, "Bob");
434 assert_eq!(profile.age, 30);
435 assert!(profile.previous_changes().contains_key("name"));
436 }
437
438 #[test]
439 fn clear_changes_removes_unsaved_history() {
440 let mut profile = TestProfile::new("Alice", 30);
441 profile.set_name("Bob");
442
443 profile.clear_changes();
444
445 assert!(!profile.changed());
446 assert!(profile.changes().is_empty());
447 assert_eq!(profile.attribute_was("name"), None);
448 }
449
450 #[test]
451 fn tracker_is_unchanged_when_new() {
452 let tracker = ChangeTracker::new();
453
454 assert!(!tracker.changed());
455 assert!(tracker.changes().is_empty());
456 }
457
458 #[test]
459 fn tracker_attribute_changed_is_false_for_unknown_name() {
460 let tracker = ChangeTracker::new();
461
462 assert!(!tracker.attribute_changed("email"));
463 }
464
465 #[test]
466 fn tracker_previous_changes_are_empty_when_new() {
467 let tracker = ChangeTracker::new();
468
469 assert!(tracker.previous_changes().is_empty());
470 }
471
472 #[test]
473 fn tracker_changes_preserve_nested_json_values() {
474 let mut tracker = ChangeTracker::new();
475 tracker.track_change(
476 "settings",
477 json!({ "theme": "dark", "flags": { "beta": false } }),
478 json!({ "theme": "light", "flags": { "beta": true } }),
479 );
480
481 assert_eq!(
482 tracker.changes().get("settings"),
483 Some(&[
484 json!({ "theme": "dark", "flags": { "beta": false } }),
485 json!({ "theme": "light", "flags": { "beta": true } }),
486 ])
487 );
488 }
489
490 #[test]
491 fn tracker_multiple_updates_keep_latest_new_value() {
492 let mut tracker = ChangeTracker::new();
493 tracker.track_change("name", json!("Alice"), json!("Bob"));
494 tracker.track_change("name", json!("Bob"), json!("Carol"));
495 tracker.track_change("name", json!("Carol"), json!("Dora"));
496
497 assert_eq!(
498 tracker.changes().get("name"),
499 Some(&[json!("Alice"), json!("Dora")])
500 );
501 }
502
503 #[test]
504 fn tracker_apply_replaces_previous_changes_instead_of_accumulating() {
505 let mut tracker = ChangeTracker::new();
506 tracker.track_change("name", json!("Alice"), json!("Bob"));
507 tracker.apply();
508 tracker.track_change("age", json!(30), json!(31));
509 tracker.apply();
510
511 assert_eq!(tracker.previous_changes().len(), 1);
512 assert!(!tracker.previous_changes().contains_key("name"));
513 assert_eq!(
514 tracker.previous_changes().get("age"),
515 Some(&[json!(30), json!(31)])
516 );
517 }
518
519 #[test]
520 fn tracker_restore_attribute_allows_retracking_from_original_value() {
521 let mut tracker = ChangeTracker::new();
522 tracker.track_change("name", json!("Alice"), json!("Bob"));
523
524 assert_eq!(tracker.restore_attribute("name"), Some(json!("Alice")));
525
526 tracker.track_change("name", json!("Alice"), json!("Carol"));
527
528 assert_eq!(tracker.attribute_was("name"), Some(&json!("Alice")));
529 assert_eq!(
530 tracker.changes().get("name"),
531 Some(&[json!("Alice"), json!("Carol")])
532 );
533 }
534
535 #[test]
536 fn tracker_restore_attribute_only_clears_requested_entry() {
537 let mut tracker = ChangeTracker::new();
538 tracker.track_change("name", json!("Alice"), json!("Bob"));
539 tracker.track_change("age", json!(30), json!(31));
540
541 let original = tracker.restore_attribute("age");
542
543 assert_eq!(original, Some(json!(30)));
544 assert!(!tracker.attribute_changed("age"));
545 assert!(tracker.attribute_changed("name"));
546 assert_eq!(tracker.attribute_was("name"), Some(&json!("Alice")));
547 }
548
549 #[test]
550 fn tracker_clear_after_unsaved_changes_drops_current_state() {
551 let mut tracker = ChangeTracker::new();
552 tracker.track_change("name", json!("Alice"), json!("Bob"));
553 tracker.track_change("age", json!(30), json!(31));
554
555 tracker.clear();
556
557 assert!(!tracker.changed());
558 assert!(tracker.changes().is_empty());
559 assert!(tracker.previous_changes().is_empty());
560 }
561
562 #[test]
563 fn tracker_apply_after_clear_keeps_previous_changes_empty() {
564 let mut tracker = ChangeTracker::new();
565 tracker.track_change("name", json!("Alice"), json!("Bob"));
566 tracker.clear();
567 tracker.apply();
568
569 assert!(tracker.previous_changes().is_empty());
570 }
571
572 #[test]
573 fn tracker_reverting_one_attribute_keeps_other_changes() {
574 let mut tracker = ChangeTracker::new();
575 tracker.track_change("name", json!("Alice"), json!("Bob"));
576 tracker.track_change("age", json!(30), json!(31));
577 tracker.track_change("name", json!("Bob"), json!("Alice"));
578
579 assert!(!tracker.attribute_changed("name"));
580 assert_eq!(tracker.changes().get("age"), Some(&[json!(30), json!(31)]));
581 }
582
583 #[test]
584 fn tracker_attribute_was_uses_last_applied_value_after_new_change() {
585 let mut tracker = ChangeTracker::new();
586 tracker.track_change("name", json!("Alice"), json!("Bob"));
587 tracker.apply();
588 tracker.track_change("name", json!("Bob"), json!("Carol"));
589
590 assert_eq!(tracker.attribute_was("name"), Some(&json!("Bob")));
591 }
592
593 #[test]
594 fn tracker_nested_values_can_revert_to_original_and_clear_state() {
595 let mut tracker = ChangeTracker::new();
596 tracker.track_change(
597 "settings",
598 json!({ "theme": "dark" }),
599 json!({ "theme": "light" }),
600 );
601 tracker.track_change(
602 "settings",
603 json!({ "theme": "light" }),
604 json!({ "theme": "dark" }),
605 );
606
607 assert!(!tracker.changed());
608 assert!(tracker.changes().is_empty());
609 }
610
611 #[test]
612 fn profile_is_clean_when_new() {
613 let profile = TestProfile::new("Alice", 30);
614
615 assert!(!profile.changed());
616 assert!(profile.changes().is_empty());
617 }
618
619 #[test]
620 fn profile_previous_changes_are_empty_when_new() {
621 let profile = TestProfile::new("Alice", 30);
622
623 assert!(profile.previous_changes().is_empty());
624 }
625
626 #[test]
627 fn profile_attribute_changed_is_false_for_unknown_attribute() {
628 let profile = TestProfile::new("Alice", 30);
629
630 assert!(!profile.attribute_changed("email"));
631 }
632
633 #[test]
634 fn profile_attribute_was_is_none_when_attribute_is_clean() {
635 let profile = TestProfile::new("Alice", 30);
636
637 assert_eq!(profile.attribute_was("name"), None);
638 }
639
640 #[test]
641 fn profile_changes_report_original_and_new_values() {
642 let mut profile = TestProfile::new("Alice", 30);
643 profile.set_name("Bob");
644 profile.set_age(31);
645
646 let changes = profile.changes();
647
648 assert_eq!(changes.get("name"), Some(&[json!("Alice"), json!("Bob")]));
649 assert_eq!(changes.get("age"), Some(&[json!(30), json!(31)]));
650 }
651
652 #[test]
653 fn profile_changed_attributes_include_all_dirty_fields() {
654 let mut profile = TestProfile::new("Alice", 30);
655 profile.set_name("Bob");
656 profile.set_age(31);
657
658 let mut changed = profile.changed_attributes();
659 changed.sort();
660
661 assert_eq!(changed, vec!["age".to_owned(), "name".to_owned()]);
662 }
663
664 #[test]
665 fn profile_restore_attributes_clears_changed_attributes() {
666 let mut profile = TestProfile::new("Alice", 30);
667 profile.set_name("Bob");
668 profile.set_age(31);
669
670 profile.restore_attributes();
671
672 assert!(profile.changed_attributes().is_empty());
673 }
674
675 #[test]
676 fn profile_changes_applied_replaces_previous_changes_on_second_save() {
677 let mut profile = TestProfile::new("Alice", 30);
678 profile.set_name("Bob");
679 profile.changes_applied();
680 profile.set_age(31);
681 profile.changes_applied();
682
683 assert_eq!(profile.previous_changes().len(), 1);
684 assert!(!profile.previous_changes().contains_key("name"));
685 assert_eq!(
686 profile.previous_changes().get("age"),
687 Some(&[json!(30), json!(31)])
688 );
689 }
690
691 #[test]
692 fn profile_restore_attributes_after_multiple_unsaved_changes_restores_saved_values() {
693 let mut profile = TestProfile::new("Alice", 30);
694 profile.set_name("Bob");
695 profile.set_age(31);
696 profile.changes_applied();
697 profile.set_name("Carol");
698 profile.set_name("Dora");
699 profile.set_age(32);
700
701 profile.restore_attributes();
702
703 assert_eq!(profile.name, "Bob");
704 assert_eq!(profile.age, 31);
705 assert!(!profile.changed());
706 }
707
708 #[test]
709 fn profile_setting_name_to_same_value_is_noop() {
710 let mut profile = TestProfile::new("Alice", 30);
711 profile.set_name("Alice");
712
713 assert!(!profile.changed());
714 assert!(profile.changes().is_empty());
715 }
716
717 #[test]
718 fn profile_setting_age_to_same_value_is_noop() {
719 let mut profile = TestProfile::new("Alice", 30);
720 profile.set_age(30);
721
722 assert!(!profile.changed());
723 assert!(profile.changes().is_empty());
724 }
725
726 #[test]
727 fn profile_clear_changes_after_save_clears_previous_changes_too() {
728 let mut profile = TestProfile::new("Alice", 30);
729 profile.set_name("Bob");
730 profile.changes_applied();
731
732 profile.clear_changes();
733
734 assert!(profile.previous_changes().is_empty());
735 assert!(!profile.changed());
736 }
737
738 #[test]
739 fn profile_changes_returns_a_detached_snapshot() {
740 let mut profile = TestProfile::new("Alice", 30);
741 profile.set_name("Bob");
742
743 let mut snapshot = profile.changes();
744 snapshot.insert("nickname".to_owned(), [json!("Ace"), json!("Bobby")]);
745
746 assert!(!profile.changes().contains_key("nickname"));
747 assert_eq!(
748 profile.changes().get("name"),
749 Some(&[json!("Alice"), json!("Bob")])
750 );
751 }
752
753 #[test]
754 fn profile_multiple_unsaved_name_changes_keep_first_original_value() {
755 let mut profile = TestProfile::new("Alice", 30);
756 profile.set_name("Bob");
757 profile.set_name("Carol");
758 profile.set_name("Dora");
759
760 assert_eq!(profile.attribute_was("name"), Some(json!("Alice")));
761 assert_eq!(
762 profile.changes().get("name"),
763 Some(&[json!("Alice"), json!("Dora")])
764 );
765 }
766
767 #[test]
768 fn profile_attribute_was_tracks_last_saved_age_after_save_and_change() {
769 let mut profile = TestProfile::new("Alice", 30);
770 profile.set_age(31);
771 profile.changes_applied();
772 profile.set_age(32);
773
774 assert_eq!(profile.attribute_was("age"), Some(json!(31)));
775 assert_eq!(profile.changes().get("age"), Some(&[json!(31), json!(32)]));
776 }
777
778 #[test]
779 fn profile_changes_applied_without_new_changes_clears_previous_changes() {
780 let mut profile = TestProfile::new("Alice", 30);
781 profile.set_name("Bob");
782 profile.changes_applied();
783
784 profile.changes_applied();
785
786 assert!(profile.previous_changes().is_empty());
787 }
788 #[test]
789 fn apply_after_reverting_all_changes_clears_previous_changes() {
790 let mut tracker = ChangeTracker::new();
791 tracker.track_change("name", json!("Alice"), json!("Bob"));
792 tracker.track_change("name", json!("Bob"), json!("Alice"));
793
794 tracker.apply();
795
796 assert!(!tracker.changed());
797 assert!(tracker.changes().is_empty());
798 assert!(tracker.previous_changes().is_empty());
799 }
800
801 #[test]
802 fn restore_attribute_after_apply_keeps_previous_changes_intact() {
803 let mut tracker = ChangeTracker::new();
804 tracker.track_change("name", json!("Alice"), json!("Bob"));
805 tracker.apply();
806
807 let restored = tracker.restore_attribute("name");
808
809 assert_eq!(restored, None);
810 assert_eq!(
811 tracker.previous_changes().get("name"),
812 Some(&[json!("Alice"), json!("Bob")])
813 );
814 }
815
816 #[test]
817 fn profile_restore_attributes_preserves_previous_changes_from_last_save() {
818 let mut profile = TestProfile::new("Alice", 30);
819 profile.set_name("Bob");
820 profile.changes_applied();
821 profile.set_name("Carol");
822
823 profile.restore_attributes();
824
825 assert_eq!(profile.name, "Bob");
826 assert!(!profile.changed());
827 assert_eq!(
828 profile.previous_changes().get("name"),
829 Some(&[json!("Alice"), json!("Bob")])
830 );
831 }
832
833 #[test]
834 fn profile_clear_changes_rebases_future_tracking_to_current_values() {
835 let mut profile = TestProfile::new("Alice", 30);
836 profile.set_name("Bob");
837
838 profile.clear_changes();
839 profile.set_name("Carol");
840
841 assert_eq!(profile.attribute_was("name"), Some(json!("Bob")));
842 assert_eq!(
843 profile.changes().get("name"),
844 Some(&[json!("Bob"), json!("Carol")])
845 );
846 assert!(profile.previous_changes().is_empty());
847 }
848 #[test]
849 fn rails_setting_new_attributes_should_not_affect_previous_changes() {
850 let mut profile = TestProfile::new("Alice", 30);
851 profile.set_name("Jericho Cane");
852 profile.set_age(31);
853 profile.changes_applied();
854
855 profile.set_name("DudeFella ManGuy");
856 profile.set_age(32);
857
858 assert_eq!(
859 profile.previous_changes().get("name"),
860 Some(&[json!("Alice"), json!("Jericho Cane")])
861 );
862 assert_eq!(
863 profile.previous_changes().get("age"),
864 Some(&[json!(30), json!(31)])
865 );
866 }
867
868 #[test]
869 fn rails_previous_value_is_preserved_when_changed_after_save() {
870 let mut profile = TestProfile::new("Alice", 30);
871 profile.set_name("Paul");
872 profile.set_age(31);
873 profile.changes_applied();
874
875 profile.set_name("John");
876 profile.set_age(32);
877
878 let mut changed = profile.changed_attributes();
879 changed.sort();
880
881 assert_eq!(changed, vec!["age".to_owned(), "name".to_owned()]);
882 assert_eq!(profile.attribute_was("name"), Some(json!("Paul")));
883 assert_eq!(profile.attribute_was("age"), Some(json!(31)));
884 assert_eq!(
885 profile.previous_changes().get("name"),
886 Some(&[json!("Alice"), json!("Paul")])
887 );
888 assert_eq!(
889 profile.previous_changes().get("age"),
890 Some(&[json!(30), json!(31)])
891 );
892 }
893
894 #[test]
895 #[ignore = "Dirty::restore_attributes restores every tracked attribute and exposes no per-attribute variant"]
896 fn rails_restore_attributes_can_restore_only_some_attributes() {}
897}