1use super::schema::{PanelType, PresentarConfig};
11use crate::playbook::schema::{
12 FalsificationConfig, MutationDef, PerformanceBudget, Playbook, State, StateMachine,
13};
14use std::collections::HashMap;
15
16#[derive(Debug, Clone)]
18pub struct FalsificationResult {
19 pub id: String,
21 pub description: String,
23 pub passed: bool,
25 pub error: Option<String>,
27}
28
29impl FalsificationResult {
30 pub fn pass(id: &str, description: &str) -> Self {
32 Self {
33 id: id.to_string(),
34 description: description.to_string(),
35 passed: true,
36 error: None,
37 }
38 }
39
40 pub fn fail(id: &str, description: &str, error: &str) -> Self {
42 Self {
43 id: id.to_string(),
44 description: description.to_string(),
45 passed: false,
46 error: Some(error.to_string()),
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct FalsificationCheck {
54 pub id: String,
56 pub category: FalsificationCategory,
58 pub description: String,
60 pub mutation: String,
62 pub expected_failure: String,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum FalsificationCategory {
69 Existence,
71 Content,
73 Color,
75 Layout,
77 Keybinding,
79 DataBinding,
81 Performance,
83 Accessibility,
85}
86
87impl FalsificationCategory {
88 pub fn name(self) -> &'static str {
90 match self {
91 Self::Existence => "Panel Existence",
92 Self::Content => "Panel Content",
93 Self::Color => "Color Consistency",
94 Self::Layout => "Layout Consistency",
95 Self::Keybinding => "Keybinding Consistency",
96 Self::DataBinding => "Data Binding",
97 Self::Performance => "Performance",
98 Self::Accessibility => "Accessibility",
99 }
100 }
101
102 pub fn id_range(self) -> (u32, u32) {
104 match self {
105 Self::Existence => (1, 14),
106 Self::Content => (15, 28),
107 Self::Color => (29, 42),
108 Self::Layout => (43, 56),
109 Self::Keybinding => (57, 70),
110 Self::DataBinding => (71, 84),
111 Self::Performance => (85, 92),
112 Self::Accessibility => (93, 100),
113 }
114 }
115}
116
117pub fn generate_all_checks() -> Vec<FalsificationCheck> {
119 let mut checks = Vec::with_capacity(100);
120
121 checks.extend(generate_existence_checks());
123
124 checks.extend(generate_content_checks());
126
127 checks.extend(generate_color_checks());
129
130 checks.extend(generate_layout_checks());
132
133 checks.extend(generate_keybinding_checks());
135
136 checks.extend(generate_data_binding_checks());
138
139 checks.extend(generate_performance_checks());
141
142 checks.extend(generate_accessibility_checks());
144
145 checks
146}
147
148fn generate_existence_checks() -> Vec<FalsificationCheck> {
150 let panels = [
151 (1, PanelType::Cpu),
152 (2, PanelType::Memory),
153 (3, PanelType::Disk),
154 (4, PanelType::Network),
155 (5, PanelType::Process),
156 (6, PanelType::Gpu),
157 (7, PanelType::Battery),
158 (8, PanelType::Sensors),
159 (9, PanelType::SensorsCompact),
160 (10, PanelType::Psi),
161 (11, PanelType::System),
162 (12, PanelType::Connections),
163 (13, PanelType::Treemap),
164 (14, PanelType::Files),
165 ];
166
167 panels
168 .iter()
169 .map(|(num, panel)| FalsificationCheck {
170 id: format!("F{:03}", num),
171 category: FalsificationCategory::Existence,
172 description: format!("{} panel exists", panel.name()),
173 mutation: format!("panels.{}.enabled = false", panel.key()),
174 expected_failure: format!("{} panel must be visible", panel.name()),
175 })
176 .collect()
177}
178
179fn generate_content_checks() -> Vec<FalsificationCheck> {
181 vec![
182 FalsificationCheck {
183 id: "F015".into(),
184 category: FalsificationCategory::Content,
185 description: "CPU shows percentage".into(),
186 mutation: "panels.cpu.show_percent = false".into(),
187 expected_failure: "CPU % must be visible".into(),
188 },
189 FalsificationCheck {
190 id: "F016".into(),
191 category: FalsificationCategory::Content,
192 description: "CPU shows cores".into(),
193 mutation: "panels.cpu.show_cores = false".into(),
194 expected_failure: "Core count must be visible".into(),
195 },
196 FalsificationCheck {
197 id: "F017".into(),
198 category: FalsificationCategory::Content,
199 description: "CPU shows frequency".into(),
200 mutation: "panels.cpu.show_frequency = false".into(),
201 expected_failure: "Frequency must be visible".into(),
202 },
203 FalsificationCheck {
204 id: "F018".into(),
205 category: FalsificationCategory::Content,
206 description: "CPU shows temperature".into(),
207 mutation: "panels.cpu.show_temperature = false".into(),
208 expected_failure: "Temperature must be visible".into(),
209 },
210 FalsificationCheck {
211 id: "F019".into(),
212 category: FalsificationCategory::Content,
213 description: "Memory shows used/total".into(),
214 mutation: "panels.memory.show_usage = false".into(),
215 expected_failure: "Memory usage must be visible".into(),
216 },
217 FalsificationCheck {
218 id: "F020".into(),
219 category: FalsificationCategory::Content,
220 description: "Memory shows ZRAM".into(),
221 mutation: "panels.memory.show_zram = false".into(),
222 expected_failure: "ZRAM ratio must be visible".into(),
223 },
224 FalsificationCheck {
225 id: "F021".into(),
226 category: FalsificationCategory::Content,
227 description: "Disk shows R/W rates".into(),
228 mutation: "panels.disk.show_io = false".into(),
229 expected_failure: "I/O rates must be visible".into(),
230 },
231 FalsificationCheck {
232 id: "F022".into(),
233 category: FalsificationCategory::Content,
234 description: "Network shows RX/TX".into(),
235 mutation: "panels.network.show_rates = false".into(),
236 expected_failure: "RX/TX must be visible".into(),
237 },
238 FalsificationCheck {
239 id: "F023".into(),
240 category: FalsificationCategory::Content,
241 description: "Process shows PID".into(),
242 mutation: "panels.process.columns -= pid".into(),
243 expected_failure: "PID column must exist".into(),
244 },
245 FalsificationCheck {
246 id: "F024".into(),
247 category: FalsificationCategory::Content,
248 description: "Process shows CPU%".into(),
249 mutation: "panels.process.columns -= cpu".into(),
250 expected_failure: "CPU% column must exist".into(),
251 },
252 FalsificationCheck {
253 id: "F025".into(),
254 category: FalsificationCategory::Content,
255 description: "Process shows MEM%".into(),
256 mutation: "panels.process.columns -= mem".into(),
257 expected_failure: "MEM% column must exist".into(),
258 },
259 FalsificationCheck {
260 id: "F026".into(),
261 category: FalsificationCategory::Content,
262 description: "GPU shows utilization".into(),
263 mutation: "panels.gpu.show_util = false".into(),
264 expected_failure: "GPU util must be visible".into(),
265 },
266 FalsificationCheck {
267 id: "F027".into(),
268 category: FalsificationCategory::Content,
269 description: "GPU shows VRAM".into(),
270 mutation: "panels.gpu.show_vram = false".into(),
271 expected_failure: "VRAM must be visible".into(),
272 },
273 FalsificationCheck {
274 id: "F028".into(),
275 category: FalsificationCategory::Content,
276 description: "Battery shows charge".into(),
277 mutation: "panels.battery.show_charge = false".into(),
278 expected_failure: "Charge must be visible".into(),
279 },
280 ]
281}
282
283fn generate_color_checks() -> Vec<FalsificationCheck> {
285 let panel_colors = [
286 (29, "cpu", "#64C8FF"),
287 (30, "memory", "#B478FF"),
288 (31, "disk", "#64B4FF"),
289 (32, "network", "#FF9664"),
290 (33, "process", "#DCC464"),
291 (34, "gpu", "#64FF96"),
292 (35, "battery", "#FFDC64"),
293 (36, "sensors", "#FF6496"),
294 (37, "psi", "#C85050"),
295 (38, "connections", "#78B4DC"),
296 (39, "files", "#B48C64"),
297 ];
298
299 let mut checks: Vec<FalsificationCheck> = panel_colors
300 .iter()
301 .map(|(num, panel, color)| FalsificationCheck {
302 id: format!("F{:03}", num),
303 category: FalsificationCategory::Color,
304 description: format!("{} border color", panel),
305 mutation: format!("theme.{}_color = #000000", panel),
306 expected_failure: format!("{} border must be {}", panel, color),
307 })
308 .collect();
309
310 checks.push(FalsificationCheck {
312 id: "F040".into(),
313 category: FalsificationCategory::Color,
314 description: "Percent 0-25% cyan".into(),
315 mutation: "percent_color(10) != cyan".into(),
316 expected_failure: "0-25% must be cyan".into(),
317 });
318 checks.push(FalsificationCheck {
319 id: "F041".into(),
320 category: FalsificationCategory::Color,
321 description: "Percent 50-75% yellow".into(),
322 mutation: "percent_color(60) != yellow".into(),
323 expected_failure: "50-75% must be yellow".into(),
324 });
325 checks.push(FalsificationCheck {
326 id: "F042".into(),
327 category: FalsificationCategory::Color,
328 description: "Percent 90-100% red".into(),
329 mutation: "percent_color(95) != red".into(),
330 expected_failure: "90-100% must be red".into(),
331 });
332
333 checks
334}
335
336fn generate_layout_checks() -> Vec<FalsificationCheck> {
338 vec![
339 FalsificationCheck {
340 id: "F043".into(),
341 category: FalsificationCategory::Layout,
342 description: "Top panels 45% height".into(),
343 mutation: "layout.top_height = 0.2".into(),
344 expected_failure: "Top must be 45% height".into(),
345 },
346 FalsificationCheck {
347 id: "F044".into(),
348 category: FalsificationCategory::Layout,
349 description: "Bottom row 55% height".into(),
350 mutation: "layout.bottom_height = 0.8".into(),
351 expected_failure: "Bottom must be 55% height".into(),
352 },
353 FalsificationCheck {
354 id: "F045".into(),
355 category: FalsificationCategory::Layout,
356 description: "Process 40% width".into(),
357 mutation: "layout.process_width = 0.1".into(),
358 expected_failure: "Process must be 40% width".into(),
359 },
360 FalsificationCheck {
361 id: "F046".into(),
362 category: FalsificationCategory::Layout,
363 description: "Connections 30% width".into(),
364 mutation: "layout.conn_width = 0.1".into(),
365 expected_failure: "Connections must be 30% width".into(),
366 },
367 FalsificationCheck {
368 id: "F047".into(),
369 category: FalsificationCategory::Layout,
370 description: "Treemap 30% width".into(),
371 mutation: "layout.tree_width = 0.1".into(),
372 expected_failure: "Treemap must be 30% width".into(),
373 },
374 FalsificationCheck {
375 id: "F048".into(),
376 category: FalsificationCategory::Layout,
377 description: "Grid snap enabled".into(),
378 mutation: "layout.snap_to_grid = false".into(),
379 expected_failure: "Grid snap must work".into(),
380 },
381 FalsificationCheck {
382 id: "F049".into(),
383 category: FalsificationCategory::Layout,
384 description: "Min panel width".into(),
385 mutation: "layout.min_panel_width = 5".into(),
386 expected_failure: "Min width must be 30".into(),
387 },
388 FalsificationCheck {
389 id: "F050".into(),
390 category: FalsificationCategory::Layout,
391 description: "Min panel height".into(),
392 mutation: "layout.min_panel_height = 2".into(),
393 expected_failure: "Min height must be 6".into(),
394 },
395 FalsificationCheck {
396 id: "F051".into(),
397 category: FalsificationCategory::Layout,
398 description: "Rounded borders".into(),
399 mutation: "layout.border_style = sharp".into(),
400 expected_failure: "Borders must be rounded".into(),
401 },
402 FalsificationCheck {
403 id: "F052".into(),
404 category: FalsificationCategory::Layout,
405 description: "Title left-aligned".into(),
406 mutation: "layout.title_align = center".into(),
407 expected_failure: "Title must be left".into(),
408 },
409 FalsificationCheck {
410 id: "F053".into(),
411 category: FalsificationCategory::Layout,
412 description: "1-char padding".into(),
413 mutation: "layout.padding = 0".into(),
414 expected_failure: "Padding must be 1".into(),
415 },
416 FalsificationCheck {
417 id: "F054".into(),
418 category: FalsificationCategory::Layout,
419 description: "Responsive resize".into(),
420 mutation: "resize(80, 24) fails".into(),
421 expected_failure: "Must handle resize".into(),
422 },
423 FalsificationCheck {
424 id: "F055".into(),
425 category: FalsificationCategory::Layout,
426 description: "Graceful degradation".into(),
427 mutation: "resize(40, 12) crashes".into(),
428 expected_failure: "Must degrade gracefully".into(),
429 },
430 FalsificationCheck {
431 id: "F056".into(),
432 category: FalsificationCategory::Layout,
433 description: "2-column adaptive".into(),
434 mutation: "columns != 2 when width > 100".into(),
435 expected_failure: "Must use 2 columns".into(),
436 },
437 ]
438}
439
440fn generate_keybinding_checks() -> Vec<FalsificationCheck> {
442 vec![
443 FalsificationCheck {
444 id: "F057".into(),
445 category: FalsificationCategory::Keybinding,
446 description: "'q' quits".into(),
447 mutation: "keybindings.quit = x".into(),
448 expected_failure: "'q' must quit".into(),
449 },
450 FalsificationCheck {
451 id: "F058".into(),
452 category: FalsificationCategory::Keybinding,
453 description: "'?' shows help".into(),
454 mutation: "keybindings.help = x".into(),
455 expected_failure: "'?' must show help".into(),
456 },
457 FalsificationCheck {
458 id: "F059".into(),
459 category: FalsificationCategory::Keybinding,
460 description: "'f' toggles FPS".into(),
461 mutation: "keybindings.toggle_fps = x".into(),
462 expected_failure: "'f' must toggle FPS".into(),
463 },
464 FalsificationCheck {
465 id: "F060".into(),
466 category: FalsificationCategory::Keybinding,
467 description: "'/' filters".into(),
468 mutation: "keybindings.filter = x".into(),
469 expected_failure: "'/' must filter".into(),
470 },
471 FalsificationCheck {
472 id: "F061".into(),
473 category: FalsificationCategory::Keybinding,
474 description: "'c' sorts by CPU".into(),
475 mutation: "keybindings.sort_cpu = x".into(),
476 expected_failure: "'c' must sort CPU".into(),
477 },
478 FalsificationCheck {
479 id: "F062".into(),
480 category: FalsificationCategory::Keybinding,
481 description: "'m' sorts by MEM".into(),
482 mutation: "keybindings.sort_mem = x".into(),
483 expected_failure: "'m' must sort MEM".into(),
484 },
485 FalsificationCheck {
486 id: "F063".into(),
487 category: FalsificationCategory::Keybinding,
488 description: "'p' sorts by PID".into(),
489 mutation: "keybindings.sort_pid = x".into(),
490 expected_failure: "'p' must sort PID".into(),
491 },
492 FalsificationCheck {
493 id: "F064".into(),
494 category: FalsificationCategory::Keybinding,
495 description: "'k' kills process".into(),
496 mutation: "keybindings.kill = x".into(),
497 expected_failure: "'k' must kill".into(),
498 },
499 FalsificationCheck {
500 id: "F065".into(),
501 category: FalsificationCategory::Keybinding,
502 description: "Enter explodes".into(),
503 mutation: "keybindings.explode = x".into(),
504 expected_failure: "Enter must explode".into(),
505 },
506 FalsificationCheck {
507 id: "F066".into(),
508 category: FalsificationCategory::Keybinding,
509 description: "Escape collapses".into(),
510 mutation: "keybindings.collapse = x".into(),
511 expected_failure: "Escape must collapse".into(),
512 },
513 FalsificationCheck {
514 id: "F067".into(),
515 category: FalsificationCategory::Keybinding,
516 description: "Tab navigates".into(),
517 mutation: "keybindings.navigate = x".into(),
518 expected_failure: "Tab must navigate".into(),
519 },
520 FalsificationCheck {
521 id: "F068".into(),
522 category: FalsificationCategory::Keybinding,
523 description: "'1' toggles CPU".into(),
524 mutation: "keybindings.toggle_1 = x".into(),
525 expected_failure: "'1' must toggle CPU".into(),
526 },
527 FalsificationCheck {
528 id: "F069".into(),
529 category: FalsificationCategory::Keybinding,
530 description: "'0' resets all".into(),
531 mutation: "keybindings.reset = x".into(),
532 expected_failure: "'0' must reset".into(),
533 },
534 FalsificationCheck {
535 id: "F070".into(),
536 category: FalsificationCategory::Keybinding,
537 description: "No key conflicts".into(),
538 mutation: "keybindings.quit = keybindings.help".into(),
539 expected_failure: "Keys must be unique".into(),
540 },
541 ]
542}
543
544fn generate_data_binding_checks() -> Vec<FalsificationCheck> {
546 vec![
547 FalsificationCheck {
548 id: "F071".into(),
549 category: FalsificationCategory::DataBinding,
550 description: "CPU data updates".into(),
551 mutation: "cpu.update_interval = 0".into(),
552 expected_failure: "CPU must update".into(),
553 },
554 FalsificationCheck {
555 id: "F072".into(),
556 category: FalsificationCategory::DataBinding,
557 description: "Memory data updates".into(),
558 mutation: "memory.update_interval = 0".into(),
559 expected_failure: "Memory must update".into(),
560 },
561 FalsificationCheck {
562 id: "F073".into(),
563 category: FalsificationCategory::DataBinding,
564 description: "Disk data updates".into(),
565 mutation: "disk.update_interval = 0".into(),
566 expected_failure: "Disk must update".into(),
567 },
568 FalsificationCheck {
569 id: "F074".into(),
570 category: FalsificationCategory::DataBinding,
571 description: "Network data updates".into(),
572 mutation: "network.update_interval = 0".into(),
573 expected_failure: "Network must update".into(),
574 },
575 FalsificationCheck {
576 id: "F075".into(),
577 category: FalsificationCategory::DataBinding,
578 description: "Process data updates".into(),
579 mutation: "process.update_interval = 0".into(),
580 expected_failure: "Process must update".into(),
581 },
582 FalsificationCheck {
583 id: "F076".into(),
584 category: FalsificationCategory::DataBinding,
585 description: "GPU data updates".into(),
586 mutation: "gpu.update_interval = 0".into(),
587 expected_failure: "GPU must update".into(),
588 },
589 FalsificationCheck {
590 id: "F077".into(),
591 category: FalsificationCategory::DataBinding,
592 description: "Sparkline history".into(),
593 mutation: "sparkline_history = 0".into(),
594 expected_failure: "History must exist".into(),
595 },
596 FalsificationCheck {
597 id: "F078".into(),
598 category: FalsificationCategory::DataBinding,
599 description: "Async data race".into(),
600 mutation: "async_delay = 5000ms".into(),
601 expected_failure: "Must handle slow data".into(),
602 },
603 FalsificationCheck {
604 id: "F079".into(),
605 category: FalsificationCategory::DataBinding,
606 description: "Missing data fallback".into(),
607 mutation: "data.cpu = null".into(),
608 expected_failure: "Must show N/A".into(),
609 },
610 FalsificationCheck {
611 id: "F080".into(),
612 category: FalsificationCategory::DataBinding,
613 description: "NaN handling".into(),
614 mutation: "data.cpu.percent = NaN".into(),
615 expected_failure: "Must handle NaN".into(),
616 },
617 FalsificationCheck {
618 id: "F081".into(),
619 category: FalsificationCategory::DataBinding,
620 description: "Negative values".into(),
621 mutation: "data.memory.used = -1".into(),
622 expected_failure: "Must clamp to 0".into(),
623 },
624 FalsificationCheck {
625 id: "F082".into(),
626 category: FalsificationCategory::DataBinding,
627 description: "Overflow values".into(),
628 mutation: "data.cpu.percent = 150".into(),
629 expected_failure: "Must clamp to 100".into(),
630 },
631 FalsificationCheck {
632 id: "F083".into(),
633 category: FalsificationCategory::DataBinding,
634 description: "Empty process list".into(),
635 mutation: "data.processes = []".into(),
636 expected_failure: "Must show empty state".into(),
637 },
638 FalsificationCheck {
639 id: "F084".into(),
640 category: FalsificationCategory::DataBinding,
641 description: "1000+ processes".into(),
642 mutation: "data.processes.len = 5000".into(),
643 expected_failure: "Must paginate".into(),
644 },
645 ]
646}
647
648fn generate_performance_checks() -> Vec<FalsificationCheck> {
650 vec![
651 FalsificationCheck {
652 id: "F085".into(),
653 category: FalsificationCategory::Performance,
654 description: "60 FPS render".into(),
655 mutation: "render_time > 16ms".into(),
656 expected_failure: "Must render in 16ms".into(),
657 },
658 FalsificationCheck {
659 id: "F086".into(),
660 category: FalsificationCategory::Performance,
661 description: "Memory stable".into(),
662 mutation: "memory_growth > 1MB/min".into(),
663 expected_failure: "Must not leak".into(),
664 },
665 FalsificationCheck {
666 id: "F087".into(),
667 category: FalsificationCategory::Performance,
668 description: "CPU < 5% idle".into(),
669 mutation: "cpu_usage > 5%".into(),
670 expected_failure: "Must be efficient".into(),
671 },
672 FalsificationCheck {
673 id: "F088".into(),
674 category: FalsificationCategory::Performance,
675 description: "Startup < 100ms".into(),
676 mutation: "startup_time > 100ms".into(),
677 expected_failure: "Must start fast".into(),
678 },
679 FalsificationCheck {
680 id: "F089".into(),
681 category: FalsificationCategory::Performance,
682 description: "Resize < 16ms".into(),
683 mutation: "resize_time > 16ms".into(),
684 expected_failure: "Must resize fast".into(),
685 },
686 FalsificationCheck {
687 id: "F090".into(),
688 category: FalsificationCategory::Performance,
689 description: "Filter O(n)".into(),
690 mutation: "filter_complexity != O(n)".into(),
691 expected_failure: "Filter must be O(n)".into(),
692 },
693 FalsificationCheck {
694 id: "F091".into(),
695 category: FalsificationCategory::Performance,
696 description: "Sort O(n log n)".into(),
697 mutation: "sort_complexity != O(n log n)".into(),
698 expected_failure: "Sort must be O(n log n)".into(),
699 },
700 FalsificationCheck {
701 id: "F092".into(),
702 category: FalsificationCategory::Performance,
703 description: "Render O(panels)".into(),
704 mutation: "render_complexity != O(p)".into(),
705 expected_failure: "Render must be O(p)".into(),
706 },
707 ]
708}
709
710fn generate_accessibility_checks() -> Vec<FalsificationCheck> {
712 vec![
713 FalsificationCheck {
714 id: "F093".into(),
715 category: FalsificationCategory::Accessibility,
716 description: "High contrast mode".into(),
717 mutation: "theme.high_contrast = false".into(),
718 expected_failure: "Must support high contrast".into(),
719 },
720 FalsificationCheck {
721 id: "F094".into(),
722 category: FalsificationCategory::Accessibility,
723 description: "Colorblind safe".into(),
724 mutation: "theme.colorblind = false".into(),
725 expected_failure: "Must be colorblind safe".into(),
726 },
727 FalsificationCheck {
728 id: "F095".into(),
729 category: FalsificationCategory::Accessibility,
730 description: "Screen reader text".into(),
731 mutation: "aria.labels = null".into(),
732 expected_failure: "Must have labels".into(),
733 },
734 FalsificationCheck {
735 id: "F096".into(),
736 category: FalsificationCategory::Accessibility,
737 description: "Keyboard-only nav".into(),
738 mutation: "mouse_only = true".into(),
739 expected_failure: "Must work keyboard-only".into(),
740 },
741 FalsificationCheck {
742 id: "F097".into(),
743 category: FalsificationCategory::Accessibility,
744 description: "Focus visible".into(),
745 mutation: "focus.visible = false".into(),
746 expected_failure: "Focus must be visible".into(),
747 },
748 FalsificationCheck {
749 id: "F098".into(),
750 category: FalsificationCategory::Accessibility,
751 description: "No flashing".into(),
752 mutation: "animation.flash = true".into(),
753 expected_failure: "No flashing content".into(),
754 },
755 FalsificationCheck {
756 id: "F099".into(),
757 category: FalsificationCategory::Accessibility,
758 description: "Text scalable".into(),
759 mutation: "text.scalable = false".into(),
760 expected_failure: "Text must scale".into(),
761 },
762 FalsificationCheck {
763 id: "F100".into(),
764 category: FalsificationCategory::Accessibility,
765 description: "Error messages clear".into(),
766 mutation: "error.verbose = false".into(),
767 expected_failure: "Errors must be clear".into(),
768 },
769 ]
770}
771
772pub fn generate_falsification_playbook(config: &PresentarConfig) -> Playbook {
774 let all_checks = generate_all_checks();
775
776 let mutations: Vec<MutationDef> = all_checks
777 .iter()
778 .filter(|check| should_include_check(check, config))
779 .map(|check| MutationDef {
780 id: check.id.clone(),
781 description: check.description.clone(),
782 mutate: check.mutation.clone(),
783 expected_failure: check.expected_failure.clone(),
784 })
785 .collect();
786
787 let mut states = HashMap::new();
789 states.insert(
790 "initial".into(),
791 State {
792 id: "initial".into(),
793 description: "Initial state for falsification testing".into(),
794 on_entry: vec![],
795 on_exit: vec![],
796 invariants: vec![],
797 final_state: false,
798 },
799 );
800 states.insert(
801 "final".into(),
802 State {
803 id: "final".into(),
804 description: "Final state - all tests complete".into(),
805 on_entry: vec![],
806 on_exit: vec![],
807 invariants: vec![],
808 final_state: true,
809 },
810 );
811
812 let machine = StateMachine {
813 id: "presentar-falsification".into(),
814 initial: "initial".into(),
815 states,
816 transitions: vec![], forbidden: vec![],
818 performance: None,
819 };
820
821 Playbook {
822 version: "1.0".into(),
823 name: "presentar-falsification".into(),
824 description: format!(
825 "Generated falsification playbook with {} mutations",
826 mutations.len()
827 ),
828 machine,
829 performance: PerformanceBudget::default(),
830 playbook: None,
831 assertions: None,
832 falsification: Some(FalsificationConfig { mutations }),
833 metadata: HashMap::new(),
834 }
835}
836
837fn should_include_check(check: &FalsificationCheck, config: &PresentarConfig) -> bool {
839 match check.category {
840 FalsificationCategory::Existence => {
841 match check.id.as_str() {
843 "F001" => config.panels.cpu.enabled,
844 "F002" => config.panels.memory.enabled,
845 "F003" => config.panels.disk.enabled,
846 "F004" => config.panels.network.enabled,
847 "F005" => config.panels.process.enabled,
848 "F006" => config.panels.gpu.enabled,
849 "F007" => config.panels.battery.enabled,
850 "F008" => config.panels.sensors.enabled,
851 "F010" => config.panels.psi.enabled,
852 "F012" => config.panels.connections.enabled,
853 "F014" => config.panels.files.enabled,
854 _ => true,
855 }
856 }
857 _ => true, }
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864
865 #[test]
866 fn test_generate_all_checks() {
867 let checks = generate_all_checks();
868 assert_eq!(checks.len(), 100);
869
870 for (i, check) in checks.iter().enumerate() {
872 let expected_id = format!("F{:03}", i + 1);
873 assert_eq!(check.id, expected_id);
874 }
875 }
876
877 #[test]
878 fn test_existence_checks() {
879 let checks = generate_existence_checks();
880 assert_eq!(checks.len(), 14);
881 assert_eq!(checks[0].id, "F001");
882 assert_eq!(checks[13].id, "F014");
883 }
884
885 #[test]
886 fn test_content_checks() {
887 let checks = generate_content_checks();
888 assert_eq!(checks.len(), 14);
889 assert_eq!(checks[0].id, "F015");
890 assert_eq!(checks[13].id, "F028");
891 }
892
893 #[test]
894 fn test_color_checks() {
895 let checks = generate_color_checks();
896 assert_eq!(checks.len(), 14);
897 assert_eq!(checks[0].id, "F029");
898 assert_eq!(checks[13].id, "F042");
899 }
900
901 #[test]
902 fn test_layout_checks() {
903 let checks = generate_layout_checks();
904 assert_eq!(checks.len(), 14);
905 assert_eq!(checks[0].id, "F043");
906 assert_eq!(checks[13].id, "F056");
907 }
908
909 #[test]
910 fn test_keybinding_checks() {
911 let checks = generate_keybinding_checks();
912 assert_eq!(checks.len(), 14);
913 assert_eq!(checks[0].id, "F057");
914 assert_eq!(checks[13].id, "F070");
915 }
916
917 #[test]
918 fn test_data_binding_checks() {
919 let checks = generate_data_binding_checks();
920 assert_eq!(checks.len(), 14);
921 assert_eq!(checks[0].id, "F071");
922 assert_eq!(checks[13].id, "F084");
923 }
924
925 #[test]
926 fn test_performance_checks() {
927 let checks = generate_performance_checks();
928 assert_eq!(checks.len(), 8);
929 assert_eq!(checks[0].id, "F085");
930 assert_eq!(checks[7].id, "F092");
931 }
932
933 #[test]
934 fn test_accessibility_checks() {
935 let checks = generate_accessibility_checks();
936 assert_eq!(checks.len(), 8);
937 assert_eq!(checks[0].id, "F093");
938 assert_eq!(checks[7].id, "F100");
939 }
940
941 #[test]
942 fn test_generate_playbook() {
943 let config = PresentarConfig::default();
944 let playbook = generate_falsification_playbook(&config);
945
946 assert_eq!(playbook.version, "1.0");
947 assert!(playbook.falsification.is_some());
948
949 let mutations = &playbook.falsification.unwrap().mutations;
950 assert!(!mutations.is_empty());
951 }
952
953 #[test]
954 fn test_category_id_range() {
955 assert_eq!(FalsificationCategory::Existence.id_range(), (1, 14));
956 assert_eq!(FalsificationCategory::Content.id_range(), (15, 28));
957 assert_eq!(FalsificationCategory::Color.id_range(), (29, 42));
958 assert_eq!(FalsificationCategory::Layout.id_range(), (43, 56));
959 assert_eq!(FalsificationCategory::Keybinding.id_range(), (57, 70));
960 assert_eq!(FalsificationCategory::DataBinding.id_range(), (71, 84));
961 assert_eq!(FalsificationCategory::Performance.id_range(), (85, 92));
962 assert_eq!(FalsificationCategory::Accessibility.id_range(), (93, 100));
963 }
964
965 #[test]
966 fn test_category_name() {
967 assert_eq!(FalsificationCategory::Existence.name(), "Panel Existence");
968 assert_eq!(FalsificationCategory::Performance.name(), "Performance");
969 }
970
971 #[test]
972 fn test_falsification_result() {
973 let pass = FalsificationResult::pass("F001", "Test");
974 assert!(pass.passed);
975 assert!(pass.error.is_none());
976
977 let fail = FalsificationResult::fail("F002", "Test", "Error");
978 assert!(!fail.passed);
979 assert_eq!(fail.error, Some("Error".into()));
980 }
981
982 #[test]
983 fn test_should_include_check_disabled_panel() {
984 let mut config = PresentarConfig::default();
985 config.panels.cpu.enabled = false;
986
987 let check = FalsificationCheck {
988 id: "F001".into(),
989 category: FalsificationCategory::Existence,
990 description: "CPU panel exists".into(),
991 mutation: "panels.cpu.enabled = false".into(),
992 expected_failure: "CPU panel must be visible".into(),
993 };
994
995 assert!(!should_include_check(&check, &config));
996 }
997
998 #[test]
999 fn test_should_include_check_enabled_panel() {
1000 let config = PresentarConfig::default();
1001
1002 let check = FalsificationCheck {
1003 id: "F001".into(),
1004 category: FalsificationCategory::Existence,
1005 description: "CPU panel exists".into(),
1006 mutation: "panels.cpu.enabled = false".into(),
1007 expected_failure: "CPU panel must be visible".into(),
1008 };
1009
1010 assert!(should_include_check(&check, &config));
1011 }
1012}