Skip to main content

jugar_probar/presentar/
falsification.rs

1//! Falsification protocol generator for presentar configurations.
2//!
3//! Generates F001-F100 falsification checks based on presentar configuration.
4//!
5//! # References
6//!
7//! - Jia & Harman (2011): Mutation Testing: From Theory to Practice
8//! - Popper (1959): The Logic of Scientific Discovery (Falsificationism)
9
10use super::schema::{PanelType, PresentarConfig};
11use crate::playbook::schema::{
12    FalsificationConfig, MutationDef, PerformanceBudget, Playbook, State, StateMachine,
13};
14use std::collections::HashMap;
15
16/// Result of a falsification check.
17#[derive(Debug, Clone)]
18pub struct FalsificationResult {
19    /// Check ID (F001-F100).
20    pub id: String,
21    /// Check description.
22    pub description: String,
23    /// Whether the check passed.
24    pub passed: bool,
25    /// Error message if failed.
26    pub error: Option<String>,
27}
28
29impl FalsificationResult {
30    /// Create a passing result.
31    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    /// Create a failing result.
41    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/// A single falsification check.
52#[derive(Debug, Clone)]
53pub struct FalsificationCheck {
54    /// Check ID (F001-F100).
55    pub id: String,
56    /// Category (existence, content, color, layout, keybinding, data, performance, accessibility).
57    pub category: FalsificationCategory,
58    /// Check description.
59    pub description: String,
60    /// Mutation to apply.
61    pub mutation: String,
62    /// Expected failure message.
63    pub expected_failure: String,
64}
65
66/// Falsification check categories.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum FalsificationCategory {
69    /// F001-F014: Panel existence.
70    Existence,
71    /// F015-F028: Panel content.
72    Content,
73    /// F029-F042: Color consistency.
74    Color,
75    /// F043-F056: Layout consistency.
76    Layout,
77    /// F057-F070: Keybinding consistency.
78    Keybinding,
79    /// F071-F084: Data binding.
80    DataBinding,
81    /// F085-F092: Performance.
82    Performance,
83    /// F093-F100: Accessibility.
84    Accessibility,
85}
86
87impl FalsificationCategory {
88    /// Get category name.
89    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    /// Get ID range for category.
103    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
117/// Generate all 100 falsification checks.
118pub fn generate_all_checks() -> Vec<FalsificationCheck> {
119    let mut checks = Vec::with_capacity(100);
120
121    // F001-F014: Panel Existence
122    checks.extend(generate_existence_checks());
123
124    // F015-F028: Panel Content
125    checks.extend(generate_content_checks());
126
127    // F029-F042: Color Consistency
128    checks.extend(generate_color_checks());
129
130    // F043-F056: Layout Consistency
131    checks.extend(generate_layout_checks());
132
133    // F057-F070: Keybinding Consistency
134    checks.extend(generate_keybinding_checks());
135
136    // F071-F084: Data Binding
137    checks.extend(generate_data_binding_checks());
138
139    // F085-F092: Performance
140    checks.extend(generate_performance_checks());
141
142    // F093-F100: Accessibility
143    checks.extend(generate_accessibility_checks());
144
145    checks
146}
147
148/// Generate F001-F014: Panel Existence checks.
149fn 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
179/// Generate F015-F028: Panel Content checks.
180fn 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
283/// Generate F029-F042: Color Consistency checks.
284fn 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    // F040-F042: Percent color gradient
311    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
336/// Generate F043-F056: Layout Consistency checks.
337fn 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
440/// Generate F057-F070: Keybinding Consistency checks.
441fn 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
544/// Generate F071-F084: Data Binding checks.
545fn 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
648/// Generate F085-F092: Performance checks.
649fn 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
710/// Generate F093-F100: Accessibility checks.
711fn 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
772/// Generate a falsification playbook from presentar configuration.
773pub 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    // Create minimal state machine for falsification-only playbook
788    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![], // No transitions needed for falsification-only
817        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
837/// Determine if a check should be included based on config.
838fn should_include_check(check: &FalsificationCheck, config: &PresentarConfig) -> bool {
839    match check.category {
840        FalsificationCategory::Existence => {
841            // Include existence check if panel is enabled
842            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, // Include all other checks
858    }
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        // Verify IDs are sequential
871        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}