1pub mod audio;
58pub mod compute;
59pub mod deterministic;
60pub mod distributed;
61pub mod event;
62pub mod pipeline;
63pub mod tui;
64pub mod web_sys_gen;
65pub mod widget;
66pub mod worker;
67
68pub use audio::{AudioBrick, AudioParam, RingBufferConfig};
70pub use compute::{
71 ComputeBrick, ElementwiseOp, ReduceKind, TensorBinding, TensorType, TileOp, TileStrategy,
72};
73pub use deterministic::{
74 BrickHistory, BrickState, DeterministicBrick, DeterministicClock, DeterministicRng,
75 ExecutionTrace, GuardSeverity, GuardViolation, GuardedBrick, InvariantGuard, StateValue,
76};
77pub use distributed::{
78 Backend, BackendSelector, BrickCoordinator, BrickDataTracker, BrickInput, BrickMessage,
79 BrickOutput, DataLocation, DistributedBrick, ExecutionMetrics, MultiBrickExecutor,
80 SchedulerStats, Subscription, TaskSpec, WorkStealingScheduler, WorkStealingTask, WorkerId,
81 WorkerQueue, WorkerStats,
82};
83pub use event::{EventBinding, EventBrick, EventHandler, EventType};
84pub use pipeline::{
85 AuditEntry, BrickPipeline, BrickStage, Checkpoint, PipelineAuditCollector, PipelineContext,
86 PipelineData, PipelineError, PipelineMetadata, PipelineResult, PrivacyTier, StageTrace,
87 ValidationLevel, ValidationMessage, ValidationResult,
88};
89pub use tui::{
90 AnalyzerBrick, CielabColor, CollectorBrick, CollectorError, PanelBrick, PanelId, PanelState,
91 RingBuffer,
92};
93pub use web_sys_gen::{
94 get_base_url, BlobUrl, CustomEventDispatcher, EventDetail, FetchClient, GeneratedWebSys,
95 GenerationMetadata, PerformanceTiming, WebSysError, GENERATION_METADATA,
96};
97pub use widget::{
98 commands_to_gpu_instances, Canvas, Constraints, CornerRadius, DrawCommand, Event, GpuInstance,
99 LayoutResult, LineCap, LineJoin, Modifiers, RecordingCanvas, Rect, RenderMetrics, Size,
100 StrokeStyle, TextStyle, Transform2D, Widget, WidgetColor, WidgetExt, WidgetMouseButton,
101 WidgetPoint,
102};
103pub use worker::{
104 BrickWorkerMessage, BrickWorkerMessageDirection, FieldType, MessageField, WorkerBrick,
105 WorkerTransition,
106};
107
108use std::time::Duration;
109
110#[derive(Debug, Clone, PartialEq)]
115pub enum BrickAssertion {
116 TextVisible,
118
119 ContrastRatio(f32),
121
122 MaxLatencyMs(u32),
124
125 ElementPresent(String),
127
128 Focusable,
130
131 Custom {
133 name: String,
135 validator_id: u64,
137 },
138}
139
140impl BrickAssertion {
141 #[must_use]
143 pub const fn text_visible() -> Self {
144 Self::TextVisible
145 }
146
147 #[must_use]
149 pub const fn contrast_ratio(ratio: f32) -> Self {
150 Self::ContrastRatio(ratio)
151 }
152
153 #[must_use]
155 pub const fn max_latency_ms(ms: u32) -> Self {
156 Self::MaxLatencyMs(ms)
157 }
158
159 #[must_use]
161 pub fn element_present(selector: impl Into<String>) -> Self {
162 Self::ElementPresent(selector.into())
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct BrickBudget {
172 pub measure_ms: u32,
174 pub layout_ms: u32,
176 pub paint_ms: u32,
178 pub total_ms: u32,
180}
181
182impl BrickBudget {
183 #[must_use]
185 pub const fn uniform(total_ms: u32) -> Self {
186 let phase_ms = total_ms / 3;
187 Self {
188 measure_ms: phase_ms,
189 layout_ms: phase_ms,
190 paint_ms: phase_ms,
191 total_ms,
192 }
193 }
194
195 #[must_use]
197 pub const fn new(measure_ms: u32, layout_ms: u32, paint_ms: u32) -> Self {
198 Self {
199 measure_ms,
200 layout_ms,
201 paint_ms,
202 total_ms: measure_ms + layout_ms + paint_ms,
203 }
204 }
205
206 #[must_use]
208 pub const fn as_duration(&self) -> Duration {
209 Duration::from_millis(self.total_ms as u64)
210 }
211}
212
213impl Default for BrickBudget {
214 fn default() -> Self {
215 Self::uniform(16)
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct BrickVerification {
223 pub passed: Vec<BrickAssertion>,
225 pub failed: Vec<(BrickAssertion, String)>,
227 pub verification_time: Duration,
229}
230
231impl BrickVerification {
232 #[must_use]
234 pub fn is_valid(&self) -> bool {
235 self.failed.is_empty()
236 }
237
238 #[must_use]
240 pub fn score(&self) -> f32 {
241 let total = self.passed.len() + self.failed.len();
242 if total == 0 {
243 1.0
244 } else {
245 self.passed.len() as f32 / total as f32
246 }
247 }
248}
249
250#[derive(Debug, Clone)]
252pub struct BudgetViolation {
253 pub brick_name: String,
255 pub budget: BrickBudget,
257 pub actual: Duration,
259 pub phase: Option<BrickPhase>,
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub enum BrickPhase {
266 Measure,
268 Layout,
270 Paint,
272}
273
274pub trait Brick: Send + Sync {
290 fn brick_name(&self) -> &'static str;
292
293 fn assertions(&self) -> &[BrickAssertion];
295
296 fn budget(&self) -> BrickBudget;
298
299 fn verify(&self) -> BrickVerification;
303
304 fn to_html(&self) -> String;
309
310 fn to_css(&self) -> String;
315
316 fn test_id(&self) -> Option<&str> {
318 None
319 }
320
321 fn can_render(&self) -> bool {
323 self.verify().is_valid()
324 }
325}
326
327#[derive(Debug, Clone)]
332pub enum BrickError {
333 AssertionFailed {
335 assertion: BrickAssertion,
337 reason: String,
339 },
340
341 BudgetExceeded(BudgetViolation),
343
344 InvalidTransition {
346 from: String,
348 to: String,
350 reason: String,
352 },
353
354 MissingChild {
356 expected: String,
358 },
359
360 HtmlGenerationFailed {
362 reason: String,
364 },
365}
366
367impl std::fmt::Display for BrickError {
368 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369 match self {
370 Self::AssertionFailed { assertion, reason } => {
371 write!(f, "Assertion {assertion:?} failed: {reason}")
372 }
373 Self::BudgetExceeded(violation) => {
374 write!(
375 f,
376 "Budget exceeded for {}: {:?} > {:?}",
377 violation.brick_name, violation.actual, violation.budget.total_ms
378 )
379 }
380 Self::InvalidTransition { from, to, reason } => {
381 write!(f, "Invalid transition {from} -> {to}: {reason}")
382 }
383 Self::MissingChild { expected } => {
384 write!(f, "Missing required child brick: {expected}")
385 }
386 Self::HtmlGenerationFailed { reason } => {
387 write!(f, "HTML generation failed: {reason}")
388 }
389 }
390 }
391}
392
393impl std::error::Error for BrickError {}
394
395pub type BrickResult<T> = Result<T, BrickError>;
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 struct TestBrick {
403 text: String,
404 visible: bool,
405 }
406
407 impl Brick for TestBrick {
408 fn brick_name(&self) -> &'static str {
409 "TestBrick"
410 }
411
412 fn assertions(&self) -> &[BrickAssertion] {
413 &[
414 BrickAssertion::TextVisible,
415 BrickAssertion::ContrastRatio(4.5),
416 ]
417 }
418
419 fn budget(&self) -> BrickBudget {
420 BrickBudget::uniform(16)
421 }
422
423 fn verify(&self) -> BrickVerification {
424 let mut passed = Vec::new();
425 let mut failed = Vec::new();
426
427 for assertion in self.assertions() {
428 match assertion {
429 BrickAssertion::TextVisible => {
430 if self.visible && !self.text.is_empty() {
431 passed.push(assertion.clone());
432 } else {
433 failed.push((assertion.clone(), "Text not visible".into()));
434 }
435 }
436 BrickAssertion::ContrastRatio(_) => {
437 passed.push(assertion.clone());
439 }
440 _ => passed.push(assertion.clone()),
441 }
442 }
443
444 BrickVerification {
445 passed,
446 failed,
447 verification_time: Duration::from_micros(100),
448 }
449 }
450
451 fn to_html(&self) -> String {
452 format!(r#"<div class="test-brick">{}</div>"#, self.text)
453 }
454
455 fn to_css(&self) -> String {
456 ".test-brick { color: #fff; background: #000; }".into()
457 }
458 }
459
460 #[test]
461 fn test_brick_verification_passes() {
462 let brick = TestBrick {
463 text: "Hello".into(),
464 visible: true,
465 };
466
467 let result = brick.verify();
468 assert!(result.is_valid());
469 assert_eq!(result.score(), 1.0);
470 }
471
472 #[test]
473 fn test_brick_verification_fails() {
474 let brick = TestBrick {
475 text: String::new(),
476 visible: false,
477 };
478
479 let result = brick.verify();
480 assert!(!result.is_valid());
481 assert!(result.score() < 1.0);
482 }
483
484 #[test]
485 fn test_budget_uniform() {
486 let budget = BrickBudget::uniform(30);
487 assert_eq!(budget.total_ms, 30);
488 assert_eq!(budget.measure_ms, 10);
489 }
490
491 #[test]
492 fn test_can_render_valid() {
493 let brick = TestBrick {
494 text: "Hello".into(),
495 visible: true,
496 };
497 assert!(brick.can_render());
498 }
499
500 #[test]
501 fn test_can_render_invalid() {
502 let brick = TestBrick {
503 text: String::new(),
504 visible: false,
505 };
506 assert!(!brick.can_render());
507 }
508
509 #[test]
510 fn test_brick_assertion_constructors() {
511 let text_vis = BrickAssertion::text_visible();
512 assert!(matches!(text_vis, BrickAssertion::TextVisible));
513
514 let contrast = BrickAssertion::contrast_ratio(4.5);
515 assert!(
516 matches!(contrast, BrickAssertion::ContrastRatio(r) if (r - 4.5).abs() < f32::EPSILON)
517 );
518
519 let latency = BrickAssertion::max_latency_ms(100);
520 assert!(matches!(latency, BrickAssertion::MaxLatencyMs(100)));
521
522 let elem = BrickAssertion::element_present("div.test");
523 assert!(matches!(elem, BrickAssertion::ElementPresent(s) if s == "div.test"));
524 }
525
526 #[test]
527 fn test_brick_assertion_focusable() {
528 let focusable = BrickAssertion::Focusable;
529 assert!(matches!(focusable, BrickAssertion::Focusable));
530 }
531
532 #[test]
533 fn test_brick_assertion_custom() {
534 let custom = BrickAssertion::Custom {
535 name: "test_assertion".into(),
536 validator_id: 42,
537 };
538 match custom {
539 BrickAssertion::Custom { name, validator_id } => {
540 assert_eq!(name, "test_assertion");
541 assert_eq!(validator_id, 42);
542 }
543 _ => panic!("Expected Custom variant"),
544 }
545 }
546
547 #[test]
548 fn test_budget_new() {
549 let budget = BrickBudget::new(5, 10, 15);
550 assert_eq!(budget.measure_ms, 5);
551 assert_eq!(budget.layout_ms, 10);
552 assert_eq!(budget.paint_ms, 15);
553 assert_eq!(budget.total_ms, 30);
554 }
555
556 #[test]
557 fn test_budget_default() {
558 let budget = BrickBudget::default();
559 assert_eq!(budget.total_ms, 16); }
561
562 #[test]
563 fn test_budget_as_duration() {
564 let budget = BrickBudget::uniform(100);
565 let duration = budget.as_duration();
566 assert_eq!(duration, Duration::from_millis(100));
567 }
568
569 #[test]
570 fn test_verification_score_empty() {
571 let verification = BrickVerification {
572 passed: vec![],
573 failed: vec![],
574 verification_time: Duration::from_micros(10),
575 };
576 assert_eq!(verification.score(), 1.0); assert!(verification.is_valid());
578 }
579
580 #[test]
581 fn test_verification_score_partial() {
582 let verification = BrickVerification {
583 passed: vec![BrickAssertion::TextVisible],
584 failed: vec![(BrickAssertion::Focusable, "Not focusable".into())],
585 verification_time: Duration::from_micros(10),
586 };
587 assert_eq!(verification.score(), 0.5);
588 assert!(!verification.is_valid());
589 }
590
591 #[test]
592 fn test_brick_phase_variants() {
593 let measure = BrickPhase::Measure;
594 let layout = BrickPhase::Layout;
595 let paint = BrickPhase::Paint;
596
597 assert!(matches!(measure, BrickPhase::Measure));
598 assert!(matches!(layout, BrickPhase::Layout));
599 assert!(matches!(paint, BrickPhase::Paint));
600 assert_ne!(measure, layout);
601 }
602
603 #[test]
604 fn test_budget_violation() {
605 let violation = BudgetViolation {
606 brick_name: "TestBrick".into(),
607 budget: BrickBudget::uniform(16),
608 actual: Duration::from_millis(50),
609 phase: Some(BrickPhase::Paint),
610 };
611 assert_eq!(violation.brick_name, "TestBrick");
612 assert_eq!(violation.phase, Some(BrickPhase::Paint));
613 }
614
615 #[test]
616 fn test_brick_to_html_css() {
617 let brick = TestBrick {
618 text: "Test".into(),
619 visible: true,
620 };
621 let html = brick.to_html();
622 let css = brick.to_css();
623
624 assert!(html.contains("test-brick"));
625 assert!(html.contains("Test"));
626 assert!(css.contains(".test-brick"));
627 }
628
629 #[test]
630 fn test_brick_name() {
631 let brick = TestBrick {
632 text: "Test".into(),
633 visible: true,
634 };
635 assert_eq!(brick.brick_name(), "TestBrick");
636 }
637
638 #[test]
639 fn test_brick_assertions_list() {
640 let brick = TestBrick {
641 text: "Test".into(),
642 visible: true,
643 };
644 let assertions = brick.assertions();
645 assert_eq!(assertions.len(), 2);
646 assert!(matches!(assertions[0], BrickAssertion::TextVisible));
647 }
648
649 #[test]
650 fn test_brick_budget_method() {
651 let brick = TestBrick {
652 text: "Test".into(),
653 visible: true,
654 };
655 let budget = brick.budget();
656 assert_eq!(budget.total_ms, 16);
657 }
658
659 #[test]
662 fn test_brick_error_display_assertion_failed() {
663 let error = BrickError::AssertionFailed {
664 assertion: BrickAssertion::TextVisible,
665 reason: "Element is hidden".into(),
666 };
667 let display = format!("{error}");
668 assert!(display.contains("Assertion"));
669 assert!(display.contains("TextVisible"));
670 assert!(display.contains("Element is hidden"));
671 }
672
673 #[test]
674 fn test_brick_error_display_budget_exceeded() {
675 let violation = BudgetViolation {
676 brick_name: "MyBrick".into(),
677 budget: BrickBudget::uniform(16),
678 actual: Duration::from_millis(100),
679 phase: None,
680 };
681 let error = BrickError::BudgetExceeded(violation);
682 let display = format!("{error}");
683 assert!(display.contains("Budget exceeded"));
684 assert!(display.contains("MyBrick"));
685 }
686
687 #[test]
688 fn test_brick_error_display_invalid_transition() {
689 let error = BrickError::InvalidTransition {
690 from: "idle".into(),
691 to: "running".into(),
692 reason: "Missing prerequisite".into(),
693 };
694 let display = format!("{error}");
695 assert!(display.contains("Invalid transition"));
696 assert!(display.contains("idle"));
697 assert!(display.contains("running"));
698 assert!(display.contains("Missing prerequisite"));
699 }
700
701 #[test]
702 fn test_brick_error_display_missing_child() {
703 let error = BrickError::MissingChild {
704 expected: "ChildWidget".into(),
705 };
706 let display = format!("{error}");
707 assert!(display.contains("Missing required child brick"));
708 assert!(display.contains("ChildWidget"));
709 }
710
711 #[test]
712 fn test_brick_error_display_html_generation_failed() {
713 let error = BrickError::HtmlGenerationFailed {
714 reason: "Template parse error".into(),
715 };
716 let display = format!("{error}");
717 assert!(display.contains("HTML generation failed"));
718 assert!(display.contains("Template parse error"));
719 }
720
721 #[test]
722 fn test_brick_error_is_std_error() {
723 let error: &dyn std::error::Error = &BrickError::MissingChild {
724 expected: "Test".into(),
725 };
726 assert!(error.source().is_none());
728 }
729
730 #[test]
733 fn test_brick_test_id_default() {
734 let brick = TestBrick {
735 text: "Test".into(),
736 visible: true,
737 };
738 assert!(brick.test_id().is_none());
740 }
741
742 #[test]
745 fn test_brick_assertion_clone() {
746 let original = BrickAssertion::Custom {
747 name: "custom_check".into(),
748 validator_id: 123,
749 };
750 let cloned = original.clone();
751 assert_eq!(original, cloned);
752 }
753
754 #[test]
755 fn test_brick_assertion_partial_eq() {
756 let a1 = BrickAssertion::ContrastRatio(4.5);
757 let a2 = BrickAssertion::ContrastRatio(4.5);
758 let a3 = BrickAssertion::ContrastRatio(7.0);
759
760 assert_eq!(a1, a2);
761 assert_ne!(a1, a3);
762 }
763
764 #[test]
765 fn test_brick_assertion_element_present_eq() {
766 let a1 = BrickAssertion::element_present("div.test");
767 let a2 = BrickAssertion::element_present("div.test");
768 let a3 = BrickAssertion::element_present("span.other");
769
770 assert_eq!(a1, a2);
771 assert_ne!(a1, a3);
772 }
773
774 #[test]
777 fn test_brick_budget_clone() {
778 let original = BrickBudget::new(5, 10, 15);
779 let cloned = original;
780 assert_eq!(original, cloned);
781 }
782
783 #[test]
784 fn test_brick_budget_partial_eq() {
785 let b1 = BrickBudget::new(5, 10, 15);
786 let b2 = BrickBudget::new(5, 10, 15);
787 let b3 = BrickBudget::new(1, 2, 3);
788
789 assert_eq!(b1, b2);
790 assert_ne!(b1, b3);
791 }
792
793 #[test]
796 fn test_brick_verification_clone() {
797 let original = BrickVerification {
798 passed: vec![BrickAssertion::TextVisible],
799 failed: vec![(BrickAssertion::Focusable, "Not focusable".into())],
800 verification_time: Duration::from_micros(50),
801 };
802 let cloned = original;
803 assert_eq!(cloned.passed.len(), 1);
804 assert_eq!(cloned.failed.len(), 1);
805 assert_eq!(cloned.verification_time, Duration::from_micros(50));
806 }
807
808 #[test]
809 fn test_brick_verification_debug() {
810 let verification = BrickVerification {
811 passed: vec![BrickAssertion::TextVisible],
812 failed: vec![],
813 verification_time: Duration::from_micros(100),
814 };
815 let debug_str = format!("{verification:?}");
816 assert!(debug_str.contains("BrickVerification"));
817 assert!(debug_str.contains("passed"));
818 assert!(debug_str.contains("TextVisible"));
819 }
820
821 #[test]
824 fn test_budget_violation_clone() {
825 let original = BudgetViolation {
826 brick_name: "ClonedBrick".into(),
827 budget: BrickBudget::uniform(16),
828 actual: Duration::from_millis(32),
829 phase: Some(BrickPhase::Measure),
830 };
831 let cloned = original;
832 assert_eq!(cloned.brick_name, "ClonedBrick");
833 assert_eq!(cloned.phase, Some(BrickPhase::Measure));
834 }
835
836 #[test]
837 fn test_budget_violation_debug() {
838 let violation = BudgetViolation {
839 brick_name: "DebugBrick".into(),
840 budget: BrickBudget::uniform(16),
841 actual: Duration::from_millis(50),
842 phase: None,
843 };
844 let debug_str = format!("{violation:?}");
845 assert!(debug_str.contains("BudgetViolation"));
846 assert!(debug_str.contains("DebugBrick"));
847 }
848
849 #[test]
850 fn test_budget_violation_no_phase() {
851 let violation = BudgetViolation {
852 brick_name: "NoPhaseBrick".into(),
853 budget: BrickBudget::uniform(16),
854 actual: Duration::from_millis(50),
855 phase: None,
856 };
857 assert!(violation.phase.is_none());
858 }
859
860 #[test]
863 fn test_brick_phase_copy() {
864 let phase = BrickPhase::Layout;
865 let copied = phase;
866 assert_eq!(phase, copied);
867 }
868
869 #[test]
870 fn test_brick_phase_clone() {
871 let phase = BrickPhase::Paint;
872 #[allow(clippy::clone_on_copy)]
873 let cloned = phase.clone();
874 assert_eq!(phase, cloned);
875 }
876
877 #[test]
878 fn test_brick_phase_debug() {
879 let phase = BrickPhase::Measure;
880 let debug_str = format!("{phase:?}");
881 assert_eq!(debug_str, "Measure");
882 }
883
884 #[test]
887 fn test_brick_assertion_debug() {
888 let assertion = BrickAssertion::MaxLatencyMs(100);
889 let debug_str = format!("{assertion:?}");
890 assert!(debug_str.contains("MaxLatencyMs"));
891 assert!(debug_str.contains("100"));
892 }
893
894 #[test]
895 fn test_brick_assertion_custom_debug() {
896 let assertion = BrickAssertion::Custom {
897 name: "my_validator".into(),
898 validator_id: 999,
899 };
900 let debug_str = format!("{assertion:?}");
901 assert!(debug_str.contains("Custom"));
902 assert!(debug_str.contains("my_validator"));
903 assert!(debug_str.contains("999"));
904 }
905
906 #[test]
909 fn test_brick_budget_debug() {
910 let budget = BrickBudget::new(5, 10, 15);
911 let debug_str = format!("{budget:?}");
912 assert!(debug_str.contains("BrickBudget"));
913 assert!(debug_str.contains("measure_ms"));
914 assert!(debug_str.contains('5'));
915 }
916
917 #[test]
920 fn test_brick_error_clone() {
921 let original = BrickError::MissingChild {
922 expected: "SomeChild".into(),
923 };
924 let cloned = original;
925 match cloned {
926 BrickError::MissingChild { expected } => {
927 assert_eq!(expected, "SomeChild");
928 }
929 _ => panic!("Expected MissingChild variant"),
930 }
931 }
932
933 #[test]
934 fn test_brick_error_debug() {
935 let error = BrickError::HtmlGenerationFailed {
936 reason: "Syntax error".into(),
937 };
938 let debug_str = format!("{error:?}");
939 assert!(debug_str.contains("HtmlGenerationFailed"));
940 assert!(debug_str.contains("Syntax error"));
941 }
942
943 #[test]
946 fn test_verification_all_failed() {
947 let verification = BrickVerification {
948 passed: vec![],
949 failed: vec![
950 (BrickAssertion::TextVisible, "Hidden".into()),
951 (BrickAssertion::Focusable, "Not focusable".into()),
952 ],
953 verification_time: Duration::from_micros(10),
954 };
955 assert_eq!(verification.score(), 0.0);
956 assert!(!verification.is_valid());
957 }
958
959 #[test]
960 fn test_verification_all_passed() {
961 let verification = BrickVerification {
962 passed: vec![
963 BrickAssertion::TextVisible,
964 BrickAssertion::Focusable,
965 BrickAssertion::ContrastRatio(4.5),
966 ],
967 failed: vec![],
968 verification_time: Duration::from_micros(10),
969 };
970 assert_eq!(verification.score(), 1.0);
971 assert!(verification.is_valid());
972 }
973
974 #[test]
975 fn test_budget_uniform_edge_case() {
976 let budget = BrickBudget::uniform(10);
978 assert_eq!(budget.measure_ms, 3);
979 assert_eq!(budget.layout_ms, 3);
980 assert_eq!(budget.paint_ms, 3);
981 assert_eq!(budget.total_ms, 10);
983 }
984
985 #[test]
986 fn test_budget_zero() {
987 let budget = BrickBudget::uniform(0);
988 assert_eq!(budget.measure_ms, 0);
989 assert_eq!(budget.layout_ms, 0);
990 assert_eq!(budget.paint_ms, 0);
991 assert_eq!(budget.total_ms, 0);
992 assert_eq!(budget.as_duration(), Duration::from_millis(0));
993 }
994
995 struct TestBrickWithId {
998 id: &'static str,
999 }
1000
1001 impl Brick for TestBrickWithId {
1002 fn brick_name(&self) -> &'static str {
1003 "TestBrickWithId"
1004 }
1005
1006 fn assertions(&self) -> &[BrickAssertion] {
1007 &[]
1008 }
1009
1010 fn budget(&self) -> BrickBudget {
1011 BrickBudget::default()
1012 }
1013
1014 fn verify(&self) -> BrickVerification {
1015 BrickVerification {
1016 passed: vec![],
1017 failed: vec![],
1018 verification_time: Duration::ZERO,
1019 }
1020 }
1021
1022 fn to_html(&self) -> String {
1023 String::new()
1024 }
1025
1026 fn to_css(&self) -> String {
1027 String::new()
1028 }
1029
1030 fn test_id(&self) -> Option<&str> {
1031 Some(self.id)
1032 }
1033 }
1034
1035 #[test]
1036 fn test_brick_with_custom_test_id() {
1037 let brick = TestBrickWithId { id: "my-test-id" };
1038 assert_eq!(brick.test_id(), Some("my-test-id"));
1039 }
1040
1041 #[test]
1042 fn test_brick_with_id_can_render() {
1043 let brick = TestBrickWithId { id: "test" };
1044 assert!(brick.can_render());
1046 }
1047
1048 #[test]
1051 fn test_all_assertion_variants_are_covered() {
1052 let variants: Vec<BrickAssertion> = vec![
1054 BrickAssertion::TextVisible,
1055 BrickAssertion::ContrastRatio(4.5),
1056 BrickAssertion::MaxLatencyMs(100),
1057 BrickAssertion::ElementPresent("div".into()),
1058 BrickAssertion::Focusable,
1059 BrickAssertion::Custom {
1060 name: "test".into(),
1061 validator_id: 1,
1062 },
1063 ];
1064
1065 for variant in &variants {
1066 let _ = format!("{variant:?}");
1068 let cloned = variant.clone();
1070 assert_eq!(variant, &cloned);
1072 }
1073 }
1074
1075 #[test]
1078 fn test_brick_verify_with_empty_text_visible() {
1079 let brick = TestBrick {
1081 text: String::new(),
1082 visible: true,
1083 };
1084 let result = brick.verify();
1085 assert!(!result.is_valid());
1087 }
1088
1089 #[test]
1090 fn test_brick_verify_with_text_not_visible() {
1091 let brick = TestBrick {
1093 text: "Some text".into(),
1094 visible: false,
1095 };
1096 let result = brick.verify();
1097 assert!(!result.is_valid());
1099 }
1100}