1#[derive(Debug, Clone)]
7pub struct SnapshotConfig {
8 pub update_snapshots: bool,
10 pub threshold: f64,
12 pub snapshot_dir: String,
14}
15
16impl Default for SnapshotConfig {
17 fn default() -> Self {
18 Self {
19 update_snapshots: false,
20 threshold: 0.01, snapshot_dir: String::from("__snapshots__"),
22 }
23 }
24}
25
26impl SnapshotConfig {
27 #[must_use]
29 pub const fn with_update(mut self, update: bool) -> Self {
30 self.update_snapshots = update;
31 self
32 }
33
34 #[must_use]
36 pub const fn with_threshold(mut self, threshold: f64) -> Self {
37 self.threshold = threshold;
38 self
39 }
40
41 #[must_use]
43 pub fn with_dir(mut self, dir: impl Into<String>) -> Self {
44 self.snapshot_dir = dir.into();
45 self
46 }
47}
48
49#[derive(Debug, Clone)]
51pub struct Snapshot {
52 pub name: String,
54 pub data: Vec<u8>,
56 pub width: u32,
58 pub height: u32,
60}
61
62impl Snapshot {
63 #[must_use]
65 pub fn new(name: impl Into<String>, data: Vec<u8>) -> Self {
66 Self {
67 name: name.into(),
68 data,
69 width: 0,
70 height: 0,
71 }
72 }
73
74 #[must_use]
76 pub const fn with_dimensions(mut self, width: u32, height: u32) -> Self {
77 self.width = width;
78 self.height = height;
79 self
80 }
81
82 #[must_use]
84 pub fn diff(&self, other: &Self) -> SnapshotDiff {
85 let mut difference_count = 0;
87 let max_len = self.data.len().max(other.data.len());
88
89 if max_len == 0 {
90 return SnapshotDiff {
91 identical: true,
92 difference_count: 0,
93 difference_percent: 0.0,
94 diff_data: Vec::new(),
95 };
96 }
97
98 for i in 0..max_len {
99 let a = self.data.get(i).copied().unwrap_or(0);
100 let b = other.data.get(i).copied().unwrap_or(0);
101 if a != b {
102 difference_count += 1;
103 }
104 }
105
106 #[allow(clippy::cast_precision_loss)] let difference_percent = (difference_count as f64 / max_len as f64) * 100.0;
108
109 SnapshotDiff {
110 identical: difference_count == 0,
111 difference_count,
112 difference_percent,
113 diff_data: Vec::new(), }
115 }
116
117 #[must_use]
119 pub fn size(&self) -> usize {
120 self.data.len()
121 }
122}
123
124#[derive(Debug, Clone)]
126pub struct SnapshotDiff {
127 pub identical: bool,
129 pub difference_count: usize,
131 pub difference_percent: f64,
133 pub diff_data: Vec<u8>,
135}
136
137impl SnapshotDiff {
138 #[must_use]
140 pub const fn is_identical(&self) -> bool {
141 self.identical
142 }
143
144 #[must_use]
146 pub fn within_threshold(&self, threshold: f64) -> bool {
147 self.difference_percent <= threshold * 100.0
148 }
149}
150
151#[cfg(test)]
152#[allow(clippy::unwrap_used, clippy::expect_used)]
153mod tests {
154 use super::*;
155
156 mod h0_snapshot_config_tests {
161 use super::*;
162
163 #[test]
164 fn h0_snap_01_config_default_update_false() {
165 let config = SnapshotConfig::default();
166 assert!(!config.update_snapshots);
167 }
168
169 #[test]
170 fn h0_snap_02_config_default_threshold() {
171 let config = SnapshotConfig::default();
172 assert!((config.threshold - 0.01).abs() < 0.001);
173 }
174
175 #[test]
176 fn h0_snap_03_config_default_dir() {
177 let config = SnapshotConfig::default();
178 assert_eq!(config.snapshot_dir, "__snapshots__");
179 }
180
181 #[test]
182 fn h0_snap_04_config_with_update_true() {
183 let config = SnapshotConfig::default().with_update(true);
184 assert!(config.update_snapshots);
185 }
186
187 #[test]
188 fn h0_snap_05_config_with_update_false() {
189 let config = SnapshotConfig::default().with_update(false);
190 assert!(!config.update_snapshots);
191 }
192
193 #[test]
194 fn h0_snap_06_config_with_threshold() {
195 let config = SnapshotConfig::default().with_threshold(0.05);
196 assert!((config.threshold - 0.05).abs() < 0.001);
197 }
198
199 #[test]
200 fn h0_snap_07_config_with_dir() {
201 let config = SnapshotConfig::default().with_dir("custom_dir");
202 assert_eq!(config.snapshot_dir, "custom_dir");
203 }
204
205 #[test]
206 fn h0_snap_08_config_builder_chain() {
207 let config = SnapshotConfig::default()
208 .with_update(true)
209 .with_threshold(0.1)
210 .with_dir("test_snaps");
211 assert!(config.update_snapshots);
212 assert!((config.threshold - 0.1).abs() < 0.001);
213 assert_eq!(config.snapshot_dir, "test_snaps");
214 }
215
216 #[test]
217 fn h0_snap_09_config_clone() {
218 let config = SnapshotConfig::default().with_threshold(0.02);
219 let cloned = config;
220 assert!((cloned.threshold - 0.02).abs() < 0.001);
221 }
222
223 #[test]
224 fn h0_snap_10_config_zero_threshold() {
225 let config = SnapshotConfig::default().with_threshold(0.0);
226 assert!((config.threshold - 0.0).abs() < f64::EPSILON);
227 }
228 }
229
230 mod h0_snapshot_tests {
231 use super::*;
232
233 #[test]
234 fn h0_snap_11_snapshot_new() {
235 let snap = Snapshot::new("test", vec![1, 2, 3]);
236 assert_eq!(snap.name, "test");
237 }
238
239 #[test]
240 fn h0_snap_12_snapshot_new_data() {
241 let snap = Snapshot::new("test", vec![10, 20, 30]);
242 assert_eq!(snap.data, vec![10, 20, 30]);
243 }
244
245 #[test]
246 fn h0_snap_13_snapshot_default_dimensions() {
247 let snap = Snapshot::new("test", vec![]);
248 assert_eq!(snap.width, 0);
249 assert_eq!(snap.height, 0);
250 }
251
252 #[test]
253 fn h0_snap_14_snapshot_with_dimensions() {
254 let snap = Snapshot::new("test", vec![]).with_dimensions(100, 200);
255 assert_eq!(snap.width, 100);
256 assert_eq!(snap.height, 200);
257 }
258
259 #[test]
260 fn h0_snap_15_snapshot_size() {
261 let snap = Snapshot::new("test", vec![1, 2, 3, 4, 5]);
262 assert_eq!(snap.size(), 5);
263 }
264
265 #[test]
266 fn h0_snap_16_snapshot_size_empty() {
267 let snap = Snapshot::new("test", vec![]);
268 assert_eq!(snap.size(), 0);
269 }
270
271 #[test]
272 fn h0_snap_17_snapshot_clone() {
273 let snap = Snapshot::new("original", vec![1, 2, 3]);
274 let cloned = snap;
275 assert_eq!(cloned.name, "original");
276 assert_eq!(cloned.data, vec![1, 2, 3]);
277 }
278
279 #[test]
280 fn h0_snap_18_snapshot_string_name() {
281 let snap = Snapshot::new(String::from("string_name"), vec![]);
282 assert_eq!(snap.name, "string_name");
283 }
284
285 #[test]
286 fn h0_snap_19_snapshot_large_data() {
287 let data: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();
288 let snap = Snapshot::new("large", data);
289 assert_eq!(snap.size(), 1000);
290 }
291
292 #[test]
293 fn h0_snap_20_snapshot_dimensions_chain() {
294 let snap = Snapshot::new("test", vec![0; 100]).with_dimensions(10, 10);
295 assert_eq!(snap.width * snap.height, 100);
296 }
297 }
298
299 mod h0_snapshot_diff_tests {
300 use super::*;
301
302 #[test]
303 fn h0_snap_21_diff_identical() {
304 let a = Snapshot::new("a", vec![1, 2, 3]);
305 let b = Snapshot::new("b", vec![1, 2, 3]);
306 let diff = a.diff(&b);
307 assert!(diff.identical);
308 }
309
310 #[test]
311 fn h0_snap_22_diff_not_identical() {
312 let a = Snapshot::new("a", vec![1, 2, 3]);
313 let b = Snapshot::new("b", vec![1, 2, 4]);
314 let diff = a.diff(&b);
315 assert!(!diff.identical);
316 }
317
318 #[test]
319 fn h0_snap_23_diff_difference_count() {
320 let a = Snapshot::new("a", vec![1, 2, 3, 4]);
321 let b = Snapshot::new("b", vec![1, 0, 3, 0]);
322 let diff = a.diff(&b);
323 assert_eq!(diff.difference_count, 2);
324 }
325
326 #[test]
327 fn h0_snap_24_diff_difference_percent() {
328 let a = Snapshot::new("a", vec![1, 2, 3, 4]);
329 let b = Snapshot::new("b", vec![0, 0, 0, 0]);
330 let diff = a.diff(&b);
331 assert!((diff.difference_percent - 100.0).abs() < 0.001);
332 }
333
334 #[test]
335 fn h0_snap_25_diff_empty_snapshots() {
336 let a = Snapshot::new("a", vec![]);
337 let b = Snapshot::new("b", vec![]);
338 let diff = a.diff(&b);
339 assert!(diff.identical);
340 }
341
342 #[test]
343 fn h0_snap_26_diff_is_identical() {
344 let a = Snapshot::new("a", vec![1, 2, 3]);
345 let b = Snapshot::new("b", vec![1, 2, 3]);
346 let diff = a.diff(&b);
347 assert!(diff.is_identical());
348 }
349
350 #[test]
351 fn h0_snap_27_diff_not_is_identical() {
352 let a = Snapshot::new("a", vec![1, 2, 3]);
353 let b = Snapshot::new("b", vec![4, 5, 6]);
354 let diff = a.diff(&b);
355 assert!(!diff.is_identical());
356 }
357
358 #[test]
359 fn h0_snap_28_diff_within_threshold_true() {
360 let a = Snapshot::new("a", vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
361 let b = Snapshot::new("b", vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 11]);
362 let diff = a.diff(&b);
363 assert!(diff.within_threshold(0.15));
365 }
366
367 #[test]
368 fn h0_snap_29_diff_within_threshold_false() {
369 let a = Snapshot::new("a", vec![1, 2, 3, 4, 5]);
370 let b = Snapshot::new("b", vec![0, 0, 0, 0, 0]);
371 let diff = a.diff(&b);
372 assert!(!diff.within_threshold(0.5));
374 }
375
376 #[test]
377 fn h0_snap_30_diff_clone() {
378 let a = Snapshot::new("a", vec![1, 2, 3]);
379 let b = Snapshot::new("b", vec![1, 2, 4]);
380 let diff = a.diff(&b);
381 let cloned = diff.clone();
382 assert_eq!(cloned.difference_count, diff.difference_count);
383 }
384 }
385
386 mod h0_snapshot_comparison_tests {
387 use super::*;
388
389 #[test]
390 fn h0_snap_31_diff_different_lengths_longer_b() {
391 let a = Snapshot::new("a", vec![1, 2]);
392 let b = Snapshot::new("b", vec![1, 2, 3, 4]);
393 let diff = a.diff(&b);
394 assert!(!diff.identical);
395 }
396
397 #[test]
398 fn h0_snap_32_diff_different_lengths_longer_a() {
399 let a = Snapshot::new("a", vec![1, 2, 3, 4]);
400 let b = Snapshot::new("b", vec![1, 2]);
401 let diff = a.diff(&b);
402 assert!(!diff.identical);
403 }
404
405 #[test]
406 fn h0_snap_33_diff_zero_percent_identical() {
407 let a = Snapshot::new("a", vec![1, 2, 3]);
408 let b = Snapshot::new("b", vec![1, 2, 3]);
409 let diff = a.diff(&b);
410 assert!(diff.difference_percent < 0.001);
411 }
412
413 #[test]
414 fn h0_snap_34_diff_fifty_percent() {
415 let a = Snapshot::new("a", vec![1, 1]);
416 let b = Snapshot::new("b", vec![1, 2]);
417 let diff = a.diff(&b);
418 assert!((diff.difference_percent - 50.0).abs() < 0.001);
419 }
420
421 #[test]
422 fn h0_snap_35_diff_data_empty() {
423 let a = Snapshot::new("a", vec![1, 2, 3]);
424 let b = Snapshot::new("b", vec![1, 2, 4]);
425 let diff = a.diff(&b);
426 assert!(diff.diff_data.is_empty());
427 }
428
429 #[test]
430 fn h0_snap_36_within_threshold_zero() {
431 let a = Snapshot::new("a", vec![1, 2, 3]);
432 let b = Snapshot::new("b", vec![1, 2, 3]);
433 let diff = a.diff(&b);
434 assert!(diff.within_threshold(0.0));
435 }
436
437 #[test]
438 fn h0_snap_37_within_threshold_one() {
439 let a = Snapshot::new("a", vec![1, 2, 3]);
440 let b = Snapshot::new("b", vec![4, 5, 6]);
441 let diff = a.diff(&b);
442 assert!(diff.within_threshold(1.0));
443 }
444
445 #[test]
446 fn h0_snap_38_single_byte_diff() {
447 let a = Snapshot::new("a", vec![255]);
448 let b = Snapshot::new("b", vec![0]);
449 let diff = a.diff(&b);
450 assert_eq!(diff.difference_count, 1);
451 assert!((diff.difference_percent - 100.0).abs() < 0.001);
452 }
453
454 #[test]
455 fn h0_snap_39_single_byte_same() {
456 let a = Snapshot::new("a", vec![128]);
457 let b = Snapshot::new("b", vec![128]);
458 let diff = a.diff(&b);
459 assert!(diff.identical);
460 }
461
462 #[test]
463 fn h0_snap_40_large_snapshot_identical() {
464 let data: Vec<u8> = vec![100; 10000];
465 let a = Snapshot::new("a", data.clone());
466 let b = Snapshot::new("b", data);
467 let diff = a.diff(&b);
468 assert!(diff.identical);
469 }
470 }
471
472 mod h0_snapshot_edge_cases {
473 use super::*;
474
475 #[test]
476 fn h0_snap_41_config_high_threshold() {
477 let config = SnapshotConfig::default().with_threshold(1.0);
478 assert!((config.threshold - 1.0).abs() < f64::EPSILON);
479 }
480
481 #[test]
482 fn h0_snap_42_snapshot_with_zero_dimensions() {
483 let snap = Snapshot::new("test", vec![1, 2, 3]).with_dimensions(0, 0);
484 assert_eq!(snap.width, 0);
485 assert_eq!(snap.height, 0);
486 }
487
488 #[test]
489 fn h0_snap_43_snapshot_dimension_overflow_check() {
490 let snap = Snapshot::new("test", vec![]).with_dimensions(u32::MAX, 1);
491 assert_eq!(snap.width, u32::MAX);
492 }
493
494 #[test]
495 fn h0_snap_44_diff_all_zeros() {
496 let a = Snapshot::new("a", vec![0, 0, 0]);
497 let b = Snapshot::new("b", vec![0, 0, 0]);
498 let diff = a.diff(&b);
499 assert!(diff.identical);
500 }
501
502 #[test]
503 fn h0_snap_45_diff_all_max() {
504 let a = Snapshot::new("a", vec![255, 255, 255]);
505 let b = Snapshot::new("b", vec![255, 255, 255]);
506 let diff = a.diff(&b);
507 assert!(diff.identical);
508 }
509
510 #[test]
511 fn h0_snap_46_snapshot_name_empty() {
512 let snap = Snapshot::new("", vec![1, 2, 3]);
513 assert_eq!(snap.name, "");
514 }
515
516 #[test]
517 fn h0_snap_47_snapshot_name_unicode() {
518 let snap = Snapshot::new("テスト_스냅샷", vec![1, 2, 3]);
519 assert_eq!(snap.name, "テスト_스냅샷");
520 }
521
522 #[test]
523 fn h0_snap_48_config_empty_dir() {
524 let config = SnapshotConfig::default().with_dir("");
525 assert_eq!(config.snapshot_dir, "");
526 }
527
528 #[test]
529 fn h0_snap_49_diff_one_empty_one_full() {
530 let a = Snapshot::new("a", vec![]);
531 let b = Snapshot::new("b", vec![1, 2, 3]);
532 let diff = a.diff(&b);
533 assert!(!diff.identical);
534 assert_eq!(diff.difference_count, 3);
535 }
536
537 #[test]
538 fn h0_snap_50_diff_full_one_empty() {
539 let a = Snapshot::new("a", vec![1, 2, 3]);
540 let b = Snapshot::new("b", vec![]);
541 let diff = a.diff(&b);
542 assert!(!diff.identical);
543 assert_eq!(diff.difference_count, 3);
544 }
545 }
546}