Skip to main content

jugar_probar/emulation/
device.rs

1//! Device Emulation (Feature 15)
2//!
3//! Emulate mobile devices, screen sizes, and device capabilities.
4//!
5//! ## EXTREME TDD: Tests written FIRST per spec
6
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// Viewport dimensions
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub struct Viewport {
13    /// Width in pixels
14    pub width: u32,
15    /// Height in pixels
16    pub height: u32,
17}
18
19impl Viewport {
20    /// Create a new viewport
21    #[must_use]
22    pub const fn new(width: u32, height: u32) -> Self {
23        Self { width, height }
24    }
25
26    /// Create a landscape version of this viewport
27    #[must_use]
28    pub const fn landscape(self) -> Self {
29        if self.width > self.height {
30            self
31        } else {
32            Self {
33                width: self.height,
34                height: self.width,
35            }
36        }
37    }
38
39    /// Create a portrait version of this viewport
40    #[must_use]
41    pub const fn portrait(self) -> Self {
42        if self.height > self.width {
43            self
44        } else {
45            Self {
46                width: self.height,
47                height: self.width,
48            }
49        }
50    }
51
52    /// Check if viewport is in landscape orientation
53    #[must_use]
54    pub const fn is_landscape(&self) -> bool {
55        self.width > self.height
56    }
57
58    /// Check if viewport is in portrait orientation
59    #[must_use]
60    pub const fn is_portrait(&self) -> bool {
61        self.height > self.width
62    }
63}
64
65impl Default for Viewport {
66    fn default() -> Self {
67        Self {
68            width: 1920,
69            height: 1080,
70        }
71    }
72}
73
74/// Touch mode configuration
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
76pub enum TouchMode {
77    /// No touch support
78    #[default]
79    None,
80    /// Single touch
81    Single,
82    /// Multi-touch
83    Multi,
84}
85
86impl TouchMode {
87    /// Check if touch is enabled
88    #[must_use]
89    pub const fn is_enabled(&self) -> bool {
90        !matches!(self, Self::None)
91    }
92}
93
94/// Device descriptor with all emulation parameters
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct DeviceDescriptor {
97    /// Device name (e.g., "iPhone 14 Pro")
98    pub name: String,
99    /// Viewport dimensions
100    pub viewport: Viewport,
101    /// User agent string
102    pub user_agent: String,
103    /// Device pixel ratio (e.g., 2.0 for Retina, 3.0 for iPhone)
104    pub device_scale_factor: f64,
105    /// Whether the device is mobile
106    pub is_mobile: bool,
107    /// Touch support
108    pub touch: TouchMode,
109    /// Whether device supports hover
110    pub has_hover: bool,
111}
112
113impl DeviceDescriptor {
114    /// Create a new device descriptor
115    #[must_use]
116    pub fn new(name: impl Into<String>) -> Self {
117        Self {
118            name: name.into(),
119            viewport: Viewport::default(),
120            user_agent: String::new(),
121            device_scale_factor: 1.0,
122            is_mobile: false,
123            touch: TouchMode::None,
124            has_hover: true,
125        }
126    }
127
128    /// Set viewport
129    #[must_use]
130    pub const fn with_viewport(mut self, viewport: Viewport) -> Self {
131        self.viewport = viewport;
132        self
133    }
134
135    /// Set viewport dimensions
136    #[must_use]
137    pub const fn with_viewport_size(mut self, width: u32, height: u32) -> Self {
138        self.viewport = Viewport::new(width, height);
139        self
140    }
141
142    /// Set user agent
143    #[must_use]
144    pub fn with_user_agent(mut self, ua: impl Into<String>) -> Self {
145        self.user_agent = ua.into();
146        self
147    }
148
149    /// Set device scale factor
150    #[must_use]
151    pub const fn with_device_scale_factor(mut self, factor: f64) -> Self {
152        self.device_scale_factor = factor;
153        self
154    }
155
156    /// Set mobile mode
157    #[must_use]
158    pub const fn with_mobile(mut self, is_mobile: bool) -> Self {
159        self.is_mobile = is_mobile;
160        self
161    }
162
163    /// Set touch mode
164    #[must_use]
165    pub const fn with_touch(mut self, touch: TouchMode) -> Self {
166        self.touch = touch;
167        self
168    }
169
170    /// Set hover support
171    #[must_use]
172    pub const fn with_hover(mut self, has_hover: bool) -> Self {
173        self.has_hover = has_hover;
174        self
175    }
176}
177
178/// Device emulator with preset device profiles
179#[derive(Debug, Default)]
180pub struct DeviceEmulator {
181    presets: HashMap<String, DeviceDescriptor>,
182}
183
184impl DeviceEmulator {
185    /// Create a new device emulator with built-in presets
186    #[must_use]
187    pub fn new() -> Self {
188        let mut emulator = Self {
189            presets: HashMap::new(),
190        };
191
192        // Add built-in device presets
193        emulator.register_preset(Self::iphone_14());
194        emulator.register_preset(Self::iphone_14_pro());
195        emulator.register_preset(Self::iphone_14_pro_max());
196        emulator.register_preset(Self::ipad_pro());
197        emulator.register_preset(Self::ipad_mini());
198        emulator.register_preset(Self::pixel_7());
199        emulator.register_preset(Self::pixel_7_pro());
200        emulator.register_preset(Self::samsung_galaxy_s23());
201        emulator.register_preset(Self::desktop_1080p());
202        emulator.register_preset(Self::desktop_1440p());
203        emulator.register_preset(Self::desktop_4k());
204
205        emulator
206    }
207
208    /// Register a custom device preset
209    pub fn register_preset(&mut self, device: DeviceDescriptor) {
210        let _ = self.presets.insert(device.name.clone(), device);
211    }
212
213    /// Get a device preset by name
214    #[must_use]
215    pub fn get_preset(&self, name: &str) -> Option<&DeviceDescriptor> {
216        self.presets.get(name)
217    }
218
219    /// Get all preset names
220    #[must_use]
221    pub fn preset_names(&self) -> Vec<&str> {
222        self.presets.keys().map(String::as_str).collect()
223    }
224
225    /// Create a custom device
226    #[must_use]
227    pub fn custom(viewport: Viewport, user_agent: &str) -> DeviceDescriptor {
228        DeviceDescriptor::new("Custom")
229            .with_viewport(viewport)
230            .with_user_agent(user_agent)
231    }
232
233    // ========================================================================
234    // iPhone Presets
235    // ========================================================================
236
237    /// iPhone 14 device preset
238    #[must_use]
239    pub fn iphone_14() -> DeviceDescriptor {
240        DeviceDescriptor::new("iPhone 14")
241            .with_viewport_size(390, 844)
242            .with_user_agent(
243                "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) \
244                AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
245            )
246            .with_device_scale_factor(3.0)
247            .with_mobile(true)
248            .with_touch(TouchMode::Multi)
249            .with_hover(false)
250    }
251
252    /// iPhone 14 Pro device preset
253    #[must_use]
254    pub fn iphone_14_pro() -> DeviceDescriptor {
255        DeviceDescriptor::new("iPhone 14 Pro")
256            .with_viewport_size(393, 852)
257            .with_user_agent(
258                "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) \
259                AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
260            )
261            .with_device_scale_factor(3.0)
262            .with_mobile(true)
263            .with_touch(TouchMode::Multi)
264            .with_hover(false)
265    }
266
267    /// iPhone 14 Pro Max device preset
268    #[must_use]
269    pub fn iphone_14_pro_max() -> DeviceDescriptor {
270        DeviceDescriptor::new("iPhone 14 Pro Max")
271            .with_viewport_size(430, 932)
272            .with_user_agent(
273                "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) \
274                AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
275            )
276            .with_device_scale_factor(3.0)
277            .with_mobile(true)
278            .with_touch(TouchMode::Multi)
279            .with_hover(false)
280    }
281
282    // ========================================================================
283    // iPad Presets
284    // ========================================================================
285
286    /// iPad Pro 12.9" device preset
287    #[must_use]
288    pub fn ipad_pro() -> DeviceDescriptor {
289        DeviceDescriptor::new("iPad Pro")
290            .with_viewport_size(1024, 1366)
291            .with_user_agent(
292                "Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) \
293                AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
294            )
295            .with_device_scale_factor(2.0)
296            .with_mobile(true)
297            .with_touch(TouchMode::Multi)
298            .with_hover(false)
299    }
300
301    /// iPad Mini device preset
302    #[must_use]
303    pub fn ipad_mini() -> DeviceDescriptor {
304        DeviceDescriptor::new("iPad Mini")
305            .with_viewport_size(768, 1024)
306            .with_user_agent(
307                "Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) \
308                AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
309            )
310            .with_device_scale_factor(2.0)
311            .with_mobile(true)
312            .with_touch(TouchMode::Multi)
313            .with_hover(false)
314    }
315
316    // ========================================================================
317    // Android Presets
318    // ========================================================================
319
320    /// Google Pixel 7 device preset
321    #[must_use]
322    pub fn pixel_7() -> DeviceDescriptor {
323        DeviceDescriptor::new("Pixel 7")
324            .with_viewport_size(412, 915)
325            .with_user_agent(
326                "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 \
327                (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36",
328            )
329            .with_device_scale_factor(2.625)
330            .with_mobile(true)
331            .with_touch(TouchMode::Multi)
332            .with_hover(false)
333    }
334
335    /// Google Pixel 7 Pro device preset
336    #[must_use]
337    pub fn pixel_7_pro() -> DeviceDescriptor {
338        DeviceDescriptor::new("Pixel 7 Pro")
339            .with_viewport_size(412, 892)
340            .with_user_agent(
341                "Mozilla/5.0 (Linux; Android 13; Pixel 7 Pro) AppleWebKit/537.36 \
342                (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36",
343            )
344            .with_device_scale_factor(3.5)
345            .with_mobile(true)
346            .with_touch(TouchMode::Multi)
347            .with_hover(false)
348    }
349
350    /// Samsung Galaxy S23 device preset
351    #[must_use]
352    pub fn samsung_galaxy_s23() -> DeviceDescriptor {
353        DeviceDescriptor::new("Samsung Galaxy S23")
354            .with_viewport_size(360, 780)
355            .with_user_agent(
356                "Mozilla/5.0 (Linux; Android 13; SM-S911B) AppleWebKit/537.36 \
357                (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36",
358            )
359            .with_device_scale_factor(3.0)
360            .with_mobile(true)
361            .with_touch(TouchMode::Multi)
362            .with_hover(false)
363    }
364
365    // ========================================================================
366    // Desktop Presets
367    // ========================================================================
368
369    /// 1080p Desktop preset
370    #[must_use]
371    pub fn desktop_1080p() -> DeviceDescriptor {
372        DeviceDescriptor::new("Desktop 1080p")
373            .with_viewport_size(1920, 1080)
374            .with_user_agent(
375                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
376                (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
377            )
378            .with_device_scale_factor(1.0)
379            .with_mobile(false)
380            .with_touch(TouchMode::None)
381            .with_hover(true)
382    }
383
384    /// 1440p Desktop preset
385    #[must_use]
386    pub fn desktop_1440p() -> DeviceDescriptor {
387        DeviceDescriptor::new("Desktop 1440p")
388            .with_viewport_size(2560, 1440)
389            .with_user_agent(
390                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
391                (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
392            )
393            .with_device_scale_factor(1.0)
394            .with_mobile(false)
395            .with_touch(TouchMode::None)
396            .with_hover(true)
397    }
398
399    /// 4K Desktop preset
400    #[must_use]
401    pub fn desktop_4k() -> DeviceDescriptor {
402        DeviceDescriptor::new("Desktop 4K")
403            .with_viewport_size(3840, 2160)
404            .with_user_agent(
405                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
406                (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
407            )
408            .with_device_scale_factor(1.5)
409            .with_mobile(false)
410            .with_touch(TouchMode::None)
411            .with_hover(true)
412    }
413}
414
415// ============================================================================
416// EXTREME TDD: Tests written FIRST per spec
417// ============================================================================
418
419#[cfg(test)]
420#[allow(clippy::unwrap_used, clippy::expect_used)]
421mod tests {
422    use super::*;
423
424    mod viewport_tests {
425        use super::*;
426
427        #[test]
428        fn test_new() {
429            let viewport = Viewport::new(800, 600);
430            assert_eq!(viewport.width, 800);
431            assert_eq!(viewport.height, 600);
432        }
433
434        #[test]
435        fn test_default() {
436            let viewport = Viewport::default();
437            assert_eq!(viewport.width, 1920);
438            assert_eq!(viewport.height, 1080);
439        }
440
441        #[test]
442        fn test_landscape() {
443            let portrait = Viewport::new(600, 800);
444            let landscape = portrait.landscape();
445            assert_eq!(landscape.width, 800);
446            assert_eq!(landscape.height, 600);
447
448            // Already landscape stays landscape
449            let already = Viewport::new(800, 600);
450            let still = already.landscape();
451            assert_eq!(still.width, 800);
452            assert_eq!(still.height, 600);
453        }
454
455        #[test]
456        fn test_portrait() {
457            let landscape = Viewport::new(800, 600);
458            let portrait = landscape.portrait();
459            assert_eq!(portrait.width, 600);
460            assert_eq!(portrait.height, 800);
461
462            // Already portrait stays portrait
463            let already = Viewport::new(600, 800);
464            let still = already.portrait();
465            assert_eq!(still.width, 600);
466            assert_eq!(still.height, 800);
467        }
468
469        #[test]
470        fn test_is_landscape() {
471            assert!(Viewport::new(800, 600).is_landscape());
472            assert!(!Viewport::new(600, 800).is_landscape());
473        }
474
475        #[test]
476        fn test_is_portrait() {
477            assert!(Viewport::new(600, 800).is_portrait());
478            assert!(!Viewport::new(800, 600).is_portrait());
479        }
480    }
481
482    mod touch_mode_tests {
483        use super::*;
484
485        #[test]
486        fn test_default() {
487            let touch = TouchMode::default();
488            assert_eq!(touch, TouchMode::None);
489        }
490
491        #[test]
492        fn test_is_enabled() {
493            assert!(!TouchMode::None.is_enabled());
494            assert!(TouchMode::Single.is_enabled());
495            assert!(TouchMode::Multi.is_enabled());
496        }
497    }
498
499    mod device_descriptor_tests {
500        use super::*;
501
502        #[test]
503        fn test_new() {
504            let device = DeviceDescriptor::new("Test Device");
505            assert_eq!(device.name, "Test Device");
506            assert!(!device.is_mobile);
507            assert!(device.user_agent.is_empty());
508        }
509
510        #[test]
511        fn test_builder_pattern() {
512            let device = DeviceDescriptor::new("Custom")
513                .with_viewport_size(390, 844)
514                .with_user_agent("Mozilla/5.0")
515                .with_device_scale_factor(3.0)
516                .with_mobile(true)
517                .with_touch(TouchMode::Multi)
518                .with_hover(false);
519
520            assert_eq!(device.viewport.width, 390);
521            assert_eq!(device.viewport.height, 844);
522            assert_eq!(device.user_agent, "Mozilla/5.0");
523            assert!((device.device_scale_factor - 3.0).abs() < f64::EPSILON);
524            assert!(device.is_mobile);
525            assert_eq!(device.touch, TouchMode::Multi);
526            assert!(!device.has_hover);
527        }
528
529        #[test]
530        fn test_with_viewport() {
531            let viewport = Viewport::new(400, 800);
532            let device = DeviceDescriptor::new("Test").with_viewport(viewport);
533            assert_eq!(device.viewport, viewport);
534        }
535    }
536
537    mod device_emulator_tests {
538        use super::*;
539
540        #[test]
541        fn test_new_has_presets() {
542            let emulator = DeviceEmulator::new();
543            assert!(!emulator.preset_names().is_empty());
544        }
545
546        #[test]
547        fn test_get_preset() {
548            let emulator = DeviceEmulator::new();
549            let iphone = emulator.get_preset("iPhone 14");
550            assert!(iphone.is_some());
551            let device = iphone.unwrap();
552            assert!(device.is_mobile);
553            assert_eq!(device.touch, TouchMode::Multi);
554        }
555
556        #[test]
557        fn test_get_nonexistent_preset() {
558            let emulator = DeviceEmulator::new();
559            assert!(emulator.get_preset("NonExistent").is_none());
560        }
561
562        #[test]
563        fn test_register_custom_preset() {
564            let mut emulator = DeviceEmulator::new();
565            let custom = DeviceDescriptor::new("My Device").with_viewport_size(500, 900);
566
567            emulator.register_preset(custom);
568
569            let device = emulator.get_preset("My Device").unwrap();
570            assert_eq!(device.viewport.width, 500);
571        }
572
573        #[test]
574        fn test_custom_device() {
575            let viewport = Viewport::new(500, 800);
576            let device = DeviceEmulator::custom(viewport, "Custom UA");
577            assert_eq!(device.name, "Custom");
578            assert_eq!(device.viewport.width, 500);
579            assert_eq!(device.user_agent, "Custom UA");
580        }
581    }
582
583    mod preset_tests {
584        use super::*;
585
586        #[test]
587        fn test_iphone_14() {
588            let device = DeviceEmulator::iphone_14();
589            assert_eq!(device.name, "iPhone 14");
590            assert_eq!(device.viewport.width, 390);
591            assert_eq!(device.viewport.height, 844);
592            assert!((device.device_scale_factor - 3.0).abs() < f64::EPSILON);
593            assert!(device.is_mobile);
594            assert!(!device.user_agent.is_empty());
595        }
596
597        #[test]
598        fn test_iphone_14_pro() {
599            let device = DeviceEmulator::iphone_14_pro();
600            assert_eq!(device.name, "iPhone 14 Pro");
601            assert_eq!(device.viewport.width, 393);
602            assert!(device.is_mobile);
603        }
604
605        #[test]
606        fn test_iphone_14_pro_max() {
607            let device = DeviceEmulator::iphone_14_pro_max();
608            assert_eq!(device.name, "iPhone 14 Pro Max");
609            assert_eq!(device.viewport.width, 430);
610            assert!(device.is_mobile);
611        }
612
613        #[test]
614        fn test_ipad_pro() {
615            let device = DeviceEmulator::ipad_pro();
616            assert_eq!(device.name, "iPad Pro");
617            assert_eq!(device.viewport.width, 1024);
618            assert!(device.is_mobile);
619        }
620
621        #[test]
622        fn test_ipad_mini() {
623            let device = DeviceEmulator::ipad_mini();
624            assert_eq!(device.name, "iPad Mini");
625            assert!(device.is_mobile);
626        }
627
628        #[test]
629        fn test_pixel_7() {
630            let device = DeviceEmulator::pixel_7();
631            assert_eq!(device.name, "Pixel 7");
632            assert_eq!(device.viewport.width, 412);
633            assert!(device.is_mobile);
634            assert!(device.user_agent.contains("Android"));
635        }
636
637        #[test]
638        fn test_pixel_7_pro() {
639            let device = DeviceEmulator::pixel_7_pro();
640            assert_eq!(device.name, "Pixel 7 Pro");
641            assert!(device.is_mobile);
642        }
643
644        #[test]
645        fn test_samsung_galaxy_s23() {
646            let device = DeviceEmulator::samsung_galaxy_s23();
647            assert_eq!(device.name, "Samsung Galaxy S23");
648            assert!(device.is_mobile);
649            assert!(device.user_agent.contains("Android"));
650        }
651
652        #[test]
653        fn test_desktop_1080p() {
654            let device = DeviceEmulator::desktop_1080p();
655            assert_eq!(device.name, "Desktop 1080p");
656            assert_eq!(device.viewport.width, 1920);
657            assert_eq!(device.viewport.height, 1080);
658            assert!(!device.is_mobile);
659            assert!(device.has_hover);
660            assert_eq!(device.touch, TouchMode::None);
661        }
662
663        #[test]
664        fn test_desktop_1440p() {
665            let device = DeviceEmulator::desktop_1440p();
666            assert_eq!(device.viewport.width, 2560);
667            assert!(!device.is_mobile);
668        }
669
670        #[test]
671        fn test_desktop_4k() {
672            let device = DeviceEmulator::desktop_4k();
673            assert_eq!(device.viewport.width, 3840);
674            assert!(!device.is_mobile);
675        }
676    }
677
678    mod preset_names {
679        use super::*;
680
681        #[test]
682        fn test_all_presets_available() {
683            let emulator = DeviceEmulator::new();
684            let names = emulator.preset_names();
685
686            assert!(names.contains(&"iPhone 14"));
687            assert!(names.contains(&"iPhone 14 Pro"));
688            assert!(names.contains(&"iPhone 14 Pro Max"));
689            assert!(names.contains(&"iPad Pro"));
690            assert!(names.contains(&"iPad Mini"));
691            assert!(names.contains(&"Pixel 7"));
692            assert!(names.contains(&"Pixel 7 Pro"));
693            assert!(names.contains(&"Samsung Galaxy S23"));
694            assert!(names.contains(&"Desktop 1080p"));
695            assert!(names.contains(&"Desktop 1440p"));
696            assert!(names.contains(&"Desktop 4K"));
697        }
698    }
699
700    // =========================================================================
701    // Hâ‚€ EXTREME TDD: Device Emulation Tests (G.3 P1)
702    // =========================================================================
703
704    mod h0_viewport_tests {
705        use super::*;
706
707        #[test]
708        fn h0_device_01_viewport_new() {
709            let viewport = Viewport::new(1024, 768);
710            assert_eq!(viewport.width, 1024);
711            assert_eq!(viewport.height, 768);
712        }
713
714        #[test]
715        fn h0_device_02_viewport_default() {
716            let viewport = Viewport::default();
717            assert_eq!(viewport.width, 1920);
718            assert_eq!(viewport.height, 1080);
719        }
720
721        #[test]
722        fn h0_device_03_viewport_landscape_from_portrait() {
723            let portrait = Viewport::new(600, 800);
724            let landscape = portrait.landscape();
725            assert_eq!(landscape.width, 800);
726            assert_eq!(landscape.height, 600);
727        }
728
729        #[test]
730        fn h0_device_04_viewport_landscape_stays_landscape() {
731            let landscape = Viewport::new(800, 600);
732            let result = landscape.landscape();
733            assert_eq!(result.width, 800);
734        }
735
736        #[test]
737        fn h0_device_05_viewport_portrait_from_landscape() {
738            let landscape = Viewport::new(800, 600);
739            let portrait = landscape.portrait();
740            assert_eq!(portrait.width, 600);
741            assert_eq!(portrait.height, 800);
742        }
743
744        #[test]
745        fn h0_device_06_viewport_portrait_stays_portrait() {
746            let portrait = Viewport::new(600, 800);
747            let result = portrait.portrait();
748            assert_eq!(result.height, 800);
749        }
750
751        #[test]
752        fn h0_device_07_viewport_is_landscape() {
753            assert!(Viewport::new(1920, 1080).is_landscape());
754            assert!(!Viewport::new(1080, 1920).is_landscape());
755        }
756
757        #[test]
758        fn h0_device_08_viewport_is_portrait() {
759            assert!(Viewport::new(1080, 1920).is_portrait());
760            assert!(!Viewport::new(1920, 1080).is_portrait());
761        }
762
763        #[test]
764        fn h0_device_09_viewport_square() {
765            let square = Viewport::new(800, 800);
766            assert!(!square.is_landscape());
767            assert!(!square.is_portrait());
768        }
769
770        #[test]
771        fn h0_device_10_viewport_equality() {
772            let v1 = Viewport::new(800, 600);
773            let v2 = Viewport::new(800, 600);
774            assert_eq!(v1, v2);
775        }
776    }
777
778    mod h0_touch_mode_tests {
779        use super::*;
780
781        #[test]
782        fn h0_device_11_touch_mode_default() {
783            let touch = TouchMode::default();
784            assert_eq!(touch, TouchMode::None);
785        }
786
787        #[test]
788        fn h0_device_12_touch_mode_none_not_enabled() {
789            assert!(!TouchMode::None.is_enabled());
790        }
791
792        #[test]
793        fn h0_device_13_touch_mode_single_enabled() {
794            assert!(TouchMode::Single.is_enabled());
795        }
796
797        #[test]
798        fn h0_device_14_touch_mode_multi_enabled() {
799            assert!(TouchMode::Multi.is_enabled());
800        }
801
802        #[test]
803        fn h0_device_15_touch_mode_equality() {
804            assert_eq!(TouchMode::Single, TouchMode::Single);
805            assert_ne!(TouchMode::Single, TouchMode::Multi);
806        }
807    }
808
809    mod h0_device_descriptor_tests {
810        use super::*;
811
812        #[test]
813        fn h0_device_16_descriptor_new() {
814            let device = DeviceDescriptor::new("Test");
815            assert_eq!(device.name, "Test");
816        }
817
818        #[test]
819        fn h0_device_17_descriptor_default_viewport() {
820            let device = DeviceDescriptor::new("Test");
821            assert_eq!(device.viewport.width, 1920);
822        }
823
824        #[test]
825        fn h0_device_18_descriptor_default_not_mobile() {
826            let device = DeviceDescriptor::new("Test");
827            assert!(!device.is_mobile);
828        }
829
830        #[test]
831        fn h0_device_19_descriptor_default_has_hover() {
832            let device = DeviceDescriptor::new("Test");
833            assert!(device.has_hover);
834        }
835
836        #[test]
837        fn h0_device_20_descriptor_with_viewport() {
838            let viewport = Viewport::new(400, 800);
839            let device = DeviceDescriptor::new("Test").with_viewport(viewport);
840            assert_eq!(device.viewport.width, 400);
841        }
842
843        #[test]
844        fn h0_device_21_descriptor_with_viewport_size() {
845            let device = DeviceDescriptor::new("Test").with_viewport_size(390, 844);
846            assert_eq!(device.viewport.width, 390);
847            assert_eq!(device.viewport.height, 844);
848        }
849
850        #[test]
851        fn h0_device_22_descriptor_with_user_agent() {
852            let device = DeviceDescriptor::new("Test").with_user_agent("Mozilla/5.0");
853            assert_eq!(device.user_agent, "Mozilla/5.0");
854        }
855
856        #[test]
857        fn h0_device_23_descriptor_with_scale_factor() {
858            let device = DeviceDescriptor::new("Test").with_device_scale_factor(3.0);
859            assert!((device.device_scale_factor - 3.0).abs() < f64::EPSILON);
860        }
861
862        #[test]
863        fn h0_device_24_descriptor_with_mobile() {
864            let device = DeviceDescriptor::new("Test").with_mobile(true);
865            assert!(device.is_mobile);
866        }
867
868        #[test]
869        fn h0_device_25_descriptor_with_touch() {
870            let device = DeviceDescriptor::new("Test").with_touch(TouchMode::Multi);
871            assert_eq!(device.touch, TouchMode::Multi);
872        }
873
874        #[test]
875        fn h0_device_26_descriptor_with_hover() {
876            let device = DeviceDescriptor::new("Test").with_hover(false);
877            assert!(!device.has_hover);
878        }
879
880        #[test]
881        fn h0_device_27_descriptor_builder_chain() {
882            let device = DeviceDescriptor::new("iPhone")
883                .with_viewport_size(390, 844)
884                .with_mobile(true)
885                .with_touch(TouchMode::Multi)
886                .with_hover(false);
887            assert!(device.is_mobile);
888            assert!(!device.has_hover);
889        }
890    }
891
892    mod h0_emulator_tests {
893        use super::*;
894
895        #[test]
896        fn h0_device_28_emulator_new_has_presets() {
897            let emulator = DeviceEmulator::new();
898            assert!(!emulator.preset_names().is_empty());
899        }
900
901        #[test]
902        fn h0_device_29_emulator_get_preset_exists() {
903            let emulator = DeviceEmulator::new();
904            assert!(emulator.get_preset("iPhone 14").is_some());
905        }
906
907        #[test]
908        fn h0_device_30_emulator_get_preset_not_exists() {
909            let emulator = DeviceEmulator::new();
910            assert!(emulator.get_preset("Unknown Device").is_none());
911        }
912
913        #[test]
914        fn h0_device_31_emulator_register_custom() {
915            let mut emulator = DeviceEmulator::new();
916            let custom = DeviceDescriptor::new("Custom Device");
917            emulator.register_preset(custom);
918            assert!(emulator.get_preset("Custom Device").is_some());
919        }
920
921        #[test]
922        fn h0_device_32_emulator_custom_device() {
923            let viewport = Viewport::new(500, 900);
924            let device = DeviceEmulator::custom(viewport, "Custom UA");
925            assert_eq!(device.name, "Custom");
926            assert_eq!(device.user_agent, "Custom UA");
927        }
928
929        #[test]
930        fn h0_device_33_emulator_preset_count() {
931            let emulator = DeviceEmulator::new();
932            assert!(emulator.preset_names().len() >= 11);
933        }
934    }
935
936    mod h0_preset_iphone_tests {
937        use super::*;
938
939        #[test]
940        fn h0_device_34_iphone_14_viewport() {
941            let device = DeviceEmulator::iphone_14();
942            assert_eq!(device.viewport.width, 390);
943            assert_eq!(device.viewport.height, 844);
944        }
945
946        #[test]
947        fn h0_device_35_iphone_14_is_mobile() {
948            let device = DeviceEmulator::iphone_14();
949            assert!(device.is_mobile);
950        }
951
952        #[test]
953        fn h0_device_36_iphone_14_touch_multi() {
954            let device = DeviceEmulator::iphone_14();
955            assert_eq!(device.touch, TouchMode::Multi);
956        }
957
958        #[test]
959        fn h0_device_37_iphone_14_no_hover() {
960            let device = DeviceEmulator::iphone_14();
961            assert!(!device.has_hover);
962        }
963
964        #[test]
965        fn h0_device_38_iphone_14_pro_viewport() {
966            let device = DeviceEmulator::iphone_14_pro();
967            assert_eq!(device.viewport.width, 393);
968        }
969
970        #[test]
971        fn h0_device_39_iphone_14_pro_max_viewport() {
972            let device = DeviceEmulator::iphone_14_pro_max();
973            assert_eq!(device.viewport.width, 430);
974        }
975    }
976
977    mod h0_preset_android_tests {
978        use super::*;
979
980        #[test]
981        fn h0_device_40_pixel_7_viewport() {
982            let device = DeviceEmulator::pixel_7();
983            assert_eq!(device.viewport.width, 412);
984        }
985
986        #[test]
987        fn h0_device_41_pixel_7_user_agent() {
988            let device = DeviceEmulator::pixel_7();
989            assert!(device.user_agent.contains("Android"));
990        }
991
992        #[test]
993        fn h0_device_42_pixel_7_pro_scale_factor() {
994            let device = DeviceEmulator::pixel_7_pro();
995            assert!((device.device_scale_factor - 3.5).abs() < 0.01);
996        }
997
998        #[test]
999        fn h0_device_43_samsung_galaxy_viewport() {
1000            let device = DeviceEmulator::samsung_galaxy_s23();
1001            assert_eq!(device.viewport.width, 360);
1002        }
1003
1004        #[test]
1005        fn h0_device_44_samsung_galaxy_mobile() {
1006            let device = DeviceEmulator::samsung_galaxy_s23();
1007            assert!(device.is_mobile);
1008        }
1009    }
1010
1011    mod h0_preset_tablet_tests {
1012        use super::*;
1013
1014        #[test]
1015        fn h0_device_45_ipad_pro_viewport() {
1016            let device = DeviceEmulator::ipad_pro();
1017            assert_eq!(device.viewport.width, 1024);
1018            assert_eq!(device.viewport.height, 1366);
1019        }
1020
1021        #[test]
1022        fn h0_device_46_ipad_mini_viewport() {
1023            let device = DeviceEmulator::ipad_mini();
1024            assert_eq!(device.viewport.width, 768);
1025        }
1026
1027        #[test]
1028        fn h0_device_47_ipad_pro_scale_factor() {
1029            let device = DeviceEmulator::ipad_pro();
1030            assert!((device.device_scale_factor - 2.0).abs() < 0.01);
1031        }
1032    }
1033
1034    mod h0_preset_desktop_tests {
1035        use super::*;
1036
1037        #[test]
1038        fn h0_device_48_desktop_1080p() {
1039            let device = DeviceEmulator::desktop_1080p();
1040            assert_eq!(device.viewport.width, 1920);
1041            assert_eq!(device.viewport.height, 1080);
1042            assert!(!device.is_mobile);
1043        }
1044
1045        #[test]
1046        fn h0_device_49_desktop_1440p() {
1047            let device = DeviceEmulator::desktop_1440p();
1048            assert_eq!(device.viewport.width, 2560);
1049            assert_eq!(device.viewport.height, 1440);
1050        }
1051
1052        #[test]
1053        fn h0_device_50_desktop_4k() {
1054            let device = DeviceEmulator::desktop_4k();
1055            assert_eq!(device.viewport.width, 3840);
1056            assert_eq!(device.viewport.height, 2160);
1057            assert!(device.has_hover);
1058        }
1059    }
1060}