1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub struct Viewport {
13 pub width: u32,
15 pub height: u32,
17}
18
19impl Viewport {
20 #[must_use]
22 pub const fn new(width: u32, height: u32) -> Self {
23 Self { width, height }
24 }
25
26 #[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 #[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 #[must_use]
54 pub const fn is_landscape(&self) -> bool {
55 self.width > self.height
56 }
57
58 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
76pub enum TouchMode {
77 #[default]
79 None,
80 Single,
82 Multi,
84}
85
86impl TouchMode {
87 #[must_use]
89 pub const fn is_enabled(&self) -> bool {
90 !matches!(self, Self::None)
91 }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct DeviceDescriptor {
97 pub name: String,
99 pub viewport: Viewport,
101 pub user_agent: String,
103 pub device_scale_factor: f64,
105 pub is_mobile: bool,
107 pub touch: TouchMode,
109 pub has_hover: bool,
111}
112
113impl DeviceDescriptor {
114 #[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 #[must_use]
130 pub const fn with_viewport(mut self, viewport: Viewport) -> Self {
131 self.viewport = viewport;
132 self
133 }
134
135 #[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 #[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 #[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 #[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 #[must_use]
165 pub const fn with_touch(mut self, touch: TouchMode) -> Self {
166 self.touch = touch;
167 self
168 }
169
170 #[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#[derive(Debug, Default)]
180pub struct DeviceEmulator {
181 presets: HashMap<String, DeviceDescriptor>,
182}
183
184impl DeviceEmulator {
185 #[must_use]
187 pub fn new() -> Self {
188 let mut emulator = Self {
189 presets: HashMap::new(),
190 };
191
192 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 pub fn register_preset(&mut self, device: DeviceDescriptor) {
210 let _ = self.presets.insert(device.name.clone(), device);
211 }
212
213 #[must_use]
215 pub fn get_preset(&self, name: &str) -> Option<&DeviceDescriptor> {
216 self.presets.get(name)
217 }
218
219 #[must_use]
221 pub fn preset_names(&self) -> Vec<&str> {
222 self.presets.keys().map(String::as_str).collect()
223 }
224
225 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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#[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 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 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 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}