//! Contract tests for the launcher layout proportions. //! //! Scope: statically guard the GTK layout constants and sizing glue used by //! the launcher shell. //! Targets: split `client/src/launcher/ui_components/*` layout modules. //! Why: the launcher is an operational control surface; accidental spacing //! regressions can hide diagnostics or make eye/device previews unusable. const UI_LAYOUT_SRC: &str = concat!( include_str!("../../client/src/launcher/ui.rs"), include_str!("../../client/src/launcher/ui/startup_window_guard.rs"), include_str!("../../client/src/launcher/ui_components/types.rs"), include_str!("../../client/src/launcher/ui_components/build_shell.rs"), include_str!("../../client/src/launcher/ui/preview_profiles.rs"), include_str!("../../client/src/launcher/ui_components/display_pane.rs"), include_str!("../../client/src/launcher/ui_components/build_device_controls.rs"), include_str!("../../client/src/launcher/ui_components/build_operations_rail.rs"), include_str!("../../client/src/launcher/ui_components/combo_helpers.rs"), include_str!("../../client/src/launcher/ui_components/control_buttons.rs"), include_str!("../../client/src/launcher/ui_components/panel_chips.rs"), include_str!("../../client/src/launcher/ui_components/style.rs"), ); fn const_i32(name: &str) -> i32 { let needle = format!("const {name}: i32 = "); let line = UI_LAYOUT_SRC .lines() .find(|line| line.trim_start().starts_with(&needle)) .unwrap_or_else(|| panic!("missing {name} constant")); line.trim_start() .trim_start_matches(&needle) .trim_end_matches(';') .parse() .unwrap_or_else(|err| panic!("invalid {name} constant: {err}")) } fn source_index(needle: &str) -> usize { UI_LAYOUT_SRC .find(needle) .unwrap_or_else(|| panic!("missing source marker: {needle}")) } #[test] fn launcher_default_size_stays_inside_1080p() { assert_eq!(const_i32("LAUNCHER_DEFAULT_WIDTH"), 1540); assert_eq!(const_i32("LAUNCHER_DEFAULT_HEIGHT"), 880); assert!(const_i32("LAUNCHER_DEFAULT_WIDTH") <= 1920); assert!( const_i32("LAUNCHER_DEFAULT_HEIGHT") <= 960, "leave room for desktop panels and window chrome on a 1080p monitor" ); assert!( !UI_LAYOUT_SRC .contains("window.set_size_request(LAUNCHER_DEFAULT_WIDTH, LAUNCHER_DEFAULT_HEIGHT);"), "the top-level window should not pin a larger minimum than the startup budget" ); assert!(UI_LAYOUT_SRC.contains("let max_width = width.saturating_sub(72).max(640) as i32;")); assert!(UI_LAYOUT_SRC.contains("let max_height = height.saturating_sub(120).max(520) as i32;")); assert!( UI_LAYOUT_SRC.contains("(1540.min(max_width), 880.min(max_height))"), "the activation path must use the same compact startup budget as the shell" ); assert!(UI_LAYOUT_SRC.contains("schedule_launcher_window_guard(app, &window, launcher_size);")); assert!( UI_LAYOUT_SRC.contains("guard_window.set_default_size(launcher_size.0, launcher_size.1);") ); } #[test] fn eye_panes_keep_the_docked_preview_footprint_without_forcing_maximized_width() { assert_eq!(const_i32("EYE_PREVIEW_MIN_WIDTH"), 532); assert_eq!(const_i32("EYE_PREVIEW_MIN_HEIGHT"), 299); assert!(UI_LAYOUT_SRC.contains("display_row.set_vexpand(false);")); assert!(UI_LAYOUT_SRC.contains("display_row.set_valign(gtk::Align::Start);")); assert!( !UI_LAYOUT_SRC.contains("display_row.set_vexpand(true);"), "a vertically expanding 16:9 eye row turns extra height into horizontal overflow" ); assert!(UI_LAYOUT_SRC.contains("preview_frame.set_hexpand(true);")); assert!(UI_LAYOUT_SRC.contains("preview_frame.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("preview_frame.set_halign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("preview_box.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("stack.set_vexpand(true);")); assert!( UI_LAYOUT_SRC.contains("caption_label.set_halign(gtk::Align::End)") || UI_LAYOUT_SRC.contains("capture_label.set_halign(gtk::Align::End)") ); assert!(UI_LAYOUT_SRC.contains("capture_label.set_ellipsize(pango::EllipsizeMode::Start);")); assert!(!UI_LAYOUT_SRC.contains("root.append(&stream_status);")); assert!(UI_LAYOUT_SRC.contains("stream_status.add_css_class(\"eye-inline-status\");")); assert!(UI_LAYOUT_SRC.contains("let header_overlay = gtk::Overlay::new();")); assert!(UI_LAYOUT_SRC.contains("header_row.append(&title_label);")); assert!(UI_LAYOUT_SRC.contains("header_row.append(&capture_label);")); assert!(UI_LAYOUT_SRC.contains("header_overlay.add_overlay(&stream_status);")); assert!( source_index("header_row.append(&title_label);") < source_index("header_row.append(&capture_label);") ); assert!( source_index("controls_grid.attach(&breakout_row, 0, 1, 1, 1);") < source_index("controls_grid.attach(&capture_actions, 1, 1, 1, 1);") ); assert!( source_index("controls_grid.attach(&capture_actions, 1, 1, 1, 1);") < source_index("controls_grid.attach(&action_button, 2, 1, 1, 1);") ); assert!(UI_LAYOUT_SRC.contains("let clip_button = gtk::Button::with_label(\"Clip\");")); assert!(UI_LAYOUT_SRC.contains("let record_button = gtk::Button::with_label(\"Record\");")); assert!(UI_LAYOUT_SRC.contains("let save_button = gtk::Button::with_label(\"Save\");")); assert!(UI_LAYOUT_SRC.contains("capture_actions.append(&clip_button);")); assert!(UI_LAYOUT_SRC.contains("capture_actions.append(&record_button);")); assert!(UI_LAYOUT_SRC.contains("capture_actions.append(&save_button);")); } #[test] fn eye_and_device_combos_use_compact_labels_so_the_rail_stays_visible() { assert!(UI_LAYOUT_SRC.contains("fn compact_capture_mode_label(")); assert!(UI_LAYOUT_SRC.contains("format!(\"{size}@{fps}\")")); assert!(UI_LAYOUT_SRC.contains("format!(\"Source {}\", compact_size_label(option.height))")); assert!(UI_LAYOUT_SRC.contains("fn shorten_input_label(")); assert!(UI_LAYOUT_SRC.contains("shorten_label_with_limit(value, 22)")); assert!( !UI_LAYOUT_SRC.contains("@ {} fps (Device H.264)"), "long capture labels force a huge GTK combo natural width" ); assert!(!UI_LAYOUT_SRC.contains("(Source Size)")); assert!(!UI_LAYOUT_SRC.contains("(Display Size)")); assert!(UI_LAYOUT_SRC.contains("combobox.compact-combo")); assert!( UI_LAYOUT_SRC .matches(".add_css_class(\"compact-combo\")") .count() >= 9, "display, media, and input combos should be allowed to shrink" ); } #[test] fn device_staging_and_testing_stay_independent_so_preview_does_not_fill_dead_height() { assert!(UI_LAYOUT_SRC.contains("staging_row.set_homogeneous(false);")); assert!(UI_LAYOUT_SRC.contains("staging_row.set_vexpand(false);")); assert!(UI_LAYOUT_SRC.contains("devices_panel.set_valign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("preview_panel.set_valign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("preview_panel.set_hexpand(true);")); assert!(UI_LAYOUT_SRC.contains("build_panel(\"Upstream Media\")")); assert!( !UI_LAYOUT_SRC.contains("gtk::SizeGroup::new(gtk::SizeGroupMode::Vertical)"), "the webcam testing column must not inherit the full height of the staging controls" ); } #[test] fn eye_placeholders_use_overlay_art_without_reflowing_the_shell() { assert!(UI_LAYOUT_SRC.contains("preview_overlay.add_overlay(&preview_placeholder);")); assert!( UI_LAYOUT_SRC.contains("preview_placeholder.set_visible(picture.paintable().is_none());") ); assert!(UI_LAYOUT_SRC.contains("stack.add_named(&placeholder_box, Some(\"placeholder\"));")); assert!(UI_LAYOUT_SRC.contains("placeholder_overlay.add_overlay(&window_placeholder);")); assert!( !UI_LAYOUT_SRC.contains("placeholder_box.add_css_class(\"display-placeholder\");"), "open-eye placeholder should sit on the clear eye surface rather than an extra box" ); assert!(UI_LAYOUT_SRC.contains("env!(\"CARGO_MANIFEST_DIR\")")); assert!(UI_LAYOUT_SRC.contains("/assets/placeholders/eye_{}_{}.png")); assert!(UI_LAYOUT_SRC.contains("picture.add_css_class(\"eye-preview-surface\");")); assert!(UI_LAYOUT_SRC.contains("preview_frame.add_css_class(\"eye-preview-surface\");")); assert!(UI_LAYOUT_SRC.contains("placeholder_frame.add_css_class(\"eye-preview-surface\");")); assert!(UI_LAYOUT_SRC.contains("overlay.eye-preview-surface")); } #[test] fn device_testing_keeps_webcam_and_mic_playback_compact() { assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_WIDTH"), 400); assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_HEIGHT"), 225); assert!(UI_LAYOUT_SRC.contains("devices_panel.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("devices_body.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("media_group.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("testing_row.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("testing_row.set_valign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("preview_panel.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("preview_body.set_hexpand(true);")); assert!(UI_LAYOUT_SRC.contains("webcam_group.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("webcam_group.set_valign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("camera_preview.set_can_shrink(true);")); assert!(UI_LAYOUT_SRC.contains("camera_preview.set_vexpand(true);")); assert!( UI_LAYOUT_SRC.contains("camera_preview_idle.add_css_class(\"display-placeholder-art\");") ); assert!( UI_LAYOUT_SRC.contains( "camera_preview_stack.add_named(&camera_preview_idle_shell, Some(\"idle\"));" ) ); assert!( UI_LAYOUT_SRC .contains("camera_preview_stack.add_named(&camera_preview_overlay, Some(\"live\"));") ); assert!(UI_LAYOUT_SRC.contains("camera_preview_stack.set_visible_child_name(\"idle\");")); assert!(UI_LAYOUT_SRC.contains("camera_preview_frame.set_vexpand(false);")); assert!(UI_LAYOUT_SRC.contains("camera_preview_frame.set_valign(gtk::Align::End);")); assert!(UI_LAYOUT_SRC.contains("playback_group.set_valign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("playback_group.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("preview_body.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("playback_body.set_valign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("audio_check_meter.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("playback_body.append(&audio_check_meter);")); assert!(UI_LAYOUT_SRC.contains("playback_body.append(µphone_replay_button);")); } #[test] fn operations_column_fills_height_and_splits_extra_space_between_logs() { assert_eq!(const_i32("SIDE_LOG_MIN_HEIGHT"), 124); assert_eq!(const_i32("SIDE_LOG_RECOVERY_BUDGET_SPLIT"), 102); assert!(UI_LAYOUT_SRC.contains("operations.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("operations.set_valign(gtk::Align::Fill);")); assert!(UI_LAYOUT_SRC.contains("diagnostics_panel.set_vexpand(true);")); assert!(UI_LAYOUT_SRC.contains("console_panel.set_vexpand(true);")); assert!( UI_LAYOUT_SRC.contains( "const SIDE_LOG_RECOVERY_MIN_HEIGHT: i32 = SIDE_LOG_MIN_HEIGHT - SIDE_LOG_RECOVERY_BUDGET_SPLIT;" ), "relay-control growth should reduce Diagnostics and Log minima through a shared split budget" ); assert_eq!( UI_LAYOUT_SRC .matches(".min_content_height(SIDE_LOG_RECOVERY_MIN_HEIGHT)") .count(), 2 ); assert_eq!( UI_LAYOUT_SRC .matches(".set_propagate_natural_height(false)") .count(), 2, "Diagnostics and Log scrollports should split height by allocation rather than content" ); assert_eq!( UI_LAYOUT_SRC .matches(".max_content_height(SIDE_LOG") .count(), 0, "the docked logs must be allowed to split extra right-rail height" ); } #[test] fn session_console_buttons_share_the_remaining_toolbar_width() { assert!( UI_LAYOUT_SRC .contains("let console_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 8);") ); assert!(UI_LAYOUT_SRC.contains("console_buttons.set_hexpand(true);")); assert!(UI_LAYOUT_SRC.contains("console_buttons.set_homogeneous(true);")); assert!(UI_LAYOUT_SRC.contains("console_copy_button.set_hexpand(true);")); assert!(UI_LAYOUT_SRC.contains("console_popout_button.set_hexpand(true);")); assert!( source_index("console_toolbar.append(&console_level_combo);") < source_index("console_toolbar.append(&console_buttons);") ); } #[test] fn status_chip_text_is_centered_inside_each_pill() { assert!(UI_LAYOUT_SRC.contains("meta.set_halign(gtk::Align::Center);")); assert!( UI_LAYOUT_SRC .matches("set_halign(gtk::Align::Center);") .count() >= 5, "chip labels and values should be horizontally centered" ); assert!( UI_LAYOUT_SRC.matches("set_xalign(0.5);").count() >= 4, "chip text lines should center their own text, not only their widgets" ); } #[test] fn relay_controls_keep_connect_inline_with_server_entry() { assert!(UI_LAYOUT_SRC.contains("build_panel(\"Relay\")")); assert_eq!(const_i32("OPERATIONS_RAIL_WIDTH"), 276); assert_eq!(const_i32("RELAY_SUBGROUP_LABEL_WIDTH"), 11); assert_eq!(const_i32("RAIL_BUTTON_WIDTH"), 86); assert_eq!(const_i32("RAIL_BUTTON_LABEL_CHARS"), 11); assert!(UI_LAYOUT_SRC.contains("let relay_grid = gtk::Grid::new();")); assert!(UI_LAYOUT_SRC.contains("server_entry.set_width_chars(11);")); assert!(UI_LAYOUT_SRC.contains("relay_grid.set_column_homogeneous(false);")); assert!(UI_LAYOUT_SRC.contains("relay_grid.attach(&server_entry, 0, 0, 1, 1);")); assert!(UI_LAYOUT_SRC.contains("let certs_button = rail_button(\"Certs\"")); assert!(UI_LAYOUT_SRC.contains("let start_button = rail_button(\"Connect\"")); assert!(UI_LAYOUT_SRC.contains("pub(crate) fn set_rail_button_label(")); assert!(UI_LAYOUT_SRC.contains("relay_grid.attach(&certs_button, 1, 0, 1, 1);")); assert!(UI_LAYOUT_SRC.contains("relay_grid.attach(&start_button, 2, 0, 1, 1);")); assert!(UI_LAYOUT_SRC.contains("stabilize_button(&certs_button, 66);")); assert!(UI_LAYOUT_SRC.contains("stabilize_button(&start_button, 76);")); assert!(UI_LAYOUT_SRC.contains("let recovery_heading = gtk::Label::new(Some(\"Recover\"));")); assert!( UI_LAYOUT_SRC .contains("let recovery_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);") ); assert!( UI_LAYOUT_SRC .contains("let recovery_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 8);") ); assert!(UI_LAYOUT_SRC.contains("recovery_buttons.set_homogeneous(true);")); assert!( UI_LAYOUT_SRC.contains("recovery_heading.set_width_chars(RELAY_SUBGROUP_LABEL_WIDTH);") ); assert!(UI_LAYOUT_SRC.contains( "let calibration_heading = gtk::Label::new(Some(\"Audio/Video\\nUpstream\\nCalibration\"));" )); assert!(UI_LAYOUT_SRC.contains("calibration_heading.set_justify(gtk::Justification::Center);")); assert!( UI_LAYOUT_SRC.contains("calibration_heading.set_width_chars(RELAY_SUBGROUP_LABEL_WIDTH);") ); assert!(UI_LAYOUT_SRC.contains("let calibration_buttons = gtk::Grid::new();")); assert!(UI_LAYOUT_SRC.contains("calibration_buttons.set_column_homogeneous(true);")); assert!(UI_LAYOUT_SRC.contains("let calibration_default_button = rail_button(")); assert!(UI_LAYOUT_SRC.contains("let calibration_factory_button = rail_button(")); assert!(UI_LAYOUT_SRC.contains("let calibration_blind_button = rail_button(")); assert!(UI_LAYOUT_SRC.contains("let calibration_minus_button = rail_button(")); assert!(UI_LAYOUT_SRC.contains("let calibration_plus_button = rail_button(")); assert!(UI_LAYOUT_SRC.contains("let calibration_rig_button = rail_button(")); assert!(UI_LAYOUT_SRC.contains("let tools_heading = gtk::Label::new(Some(\"Tools\"));")); assert!( UI_LAYOUT_SRC.contains("let tools_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);") ); assert!( UI_LAYOUT_SRC .contains("let tools_buttons = gtk::Box::new(gtk::Orientation::Horizontal, 8);") ); assert!(UI_LAYOUT_SRC.contains("tools_buttons.set_homogeneous(true);")); assert!(UI_LAYOUT_SRC.contains("tools_heading.set_width_chars(RELAY_SUBGROUP_LABEL_WIDTH);")); assert!(UI_LAYOUT_SRC.contains("let clipboard_button = rail_button(\"Clipboard\"")); assert!(UI_LAYOUT_SRC.contains("let usb_recover_button = rail_button(\"USB\"")); assert!(UI_LAYOUT_SRC.contains("let uac_recover_button = rail_button(\"UAC\"")); assert!(UI_LAYOUT_SRC.contains("let uvc_recover_button = rail_button(\"UVC\"")); assert!(UI_LAYOUT_SRC.contains("recovery_buttons.append(&usb_recover_button);")); assert!(UI_LAYOUT_SRC.contains("recovery_buttons.append(&uac_recover_button);")); assert!(UI_LAYOUT_SRC.contains("recovery_buttons.append(&uvc_recover_button);")); assert!( source_index("let recovery_heading = gtk::Label::new(Some(\"Recover\"));") < source_index( "let calibration_heading = gtk::Label::new(Some(\"Audio/Video\\nUpstream\\nCalibration\"));" ) ); assert!( source_index( "let calibration_heading = gtk::Label::new(Some(\"Audio/Video\\nUpstream\\nCalibration\"));" ) < source_index("let power_heading = gtk::Label::new(Some(\"GPIO Power\"));") ); assert!(UI_LAYOUT_SRC.contains("tools_buttons.append(&clipboard_button);")); assert!(!UI_LAYOUT_SRC.contains("Gate Probe")); assert!(UI_LAYOUT_SRC.contains("text.set_ellipsize(pango::EllipsizeMode::End);")); assert!( source_index("relay_grid.attach(&server_entry, 0, 0, 1, 1);") < source_index("relay_grid.attach(&certs_button, 1, 0, 1, 1);") ); assert!( source_index("relay_grid.attach(&certs_button, 1, 0, 1, 1);") < source_index("relay_grid.attach(&start_button, 2, 0, 1, 1);") ); } #[test] fn media_controls_own_stream_toggles_and_inline_gain_controls() { assert!(!UI_LAYOUT_SRC.contains("Remote Audio")); assert!( UI_LAYOUT_SRC.contains("let power_shell = gtk::Box::new(gtk::Orientation::Vertical, 6);") ); assert!(!UI_LAYOUT_SRC.contains("let channel_heading = gtk::Label::new(Some(\"Streams\"));")); assert!(UI_LAYOUT_SRC.contains("gtk::ToggleButton::with_label(\"Camera\")")); assert!(UI_LAYOUT_SRC.contains("gtk::ToggleButton::with_label(\"Mic\")")); assert!(UI_LAYOUT_SRC.contains("gtk::ToggleButton::with_label(\"Speaker\")")); assert!(UI_LAYOUT_SRC.contains("camera_channel_toggle.add_css_class(\"media-toggle\");")); assert!(UI_LAYOUT_SRC.contains("audio_channel_toggle.add_css_class(\"media-toggle\");")); assert!(UI_LAYOUT_SRC.contains("microphone_channel_toggle.add_css_class(\"media-toggle\");")); assert!(UI_LAYOUT_SRC.contains("button.media-toggle:checked")); assert!(UI_LAYOUT_SRC.contains("let camera_quality_combo = gtk::ComboBoxText::new();")); assert!(UI_LAYOUT_SRC.contains("sync_camera_quality_combo(")); assert!(UI_LAYOUT_SRC.contains("camera_quality_combo.set_size_request(88, -1);")); assert!( UI_LAYOUT_SRC .contains("let camera_selectors = gtk::Box::new(gtk::Orientation::Horizontal, 6);") ); assert!(UI_LAYOUT_SRC.contains("camera_selectors.append(&camera_combo);")); assert!(UI_LAYOUT_SRC.contains("camera_selectors.append(&camera_quality_combo);")); assert!( source_index("camera_selectors.append(&camera_combo);") < source_index("camera_selectors.append(&camera_quality_combo);") ); assert!( UI_LAYOUT_SRC .contains("let speaker_selectors = gtk::Box::new(gtk::Orientation::Horizontal, 6);") ); assert!(UI_LAYOUT_SRC.contains("speaker_selectors.append(&speaker_combo);")); assert!(UI_LAYOUT_SRC.contains("speaker_selectors.append(&audio_gain_scale);")); assert!(UI_LAYOUT_SRC.contains("label_widget.set_width_chars(7);")); assert!( UI_LAYOUT_SRC .contains("let microphone_selectors = gtk::Box::new(gtk::Orientation::Horizontal, 6);") ); assert!(UI_LAYOUT_SRC.contains("microphone_selectors.append(µphone_combo);")); assert!(UI_LAYOUT_SRC.contains("microphone_selectors.append(&mic_gain_scale);")); assert_eq!( UI_LAYOUT_SRC .matches("attach_device_control_row(\n &media_grid") .count(), 3 ); assert!(!UI_LAYOUT_SRC.contains("camera_combo.append(Some(\"auto\")")); assert!(!UI_LAYOUT_SRC.contains("speaker_combo.append(Some(\"auto\")")); assert!(!UI_LAYOUT_SRC.contains("microphone_combo.append(Some(\"auto\")")); assert!(UI_LAYOUT_SRC.contains("let power_heading = gtk::Label::new(Some(\"GPIO Power\"));")); assert!(UI_LAYOUT_SRC.contains("power_row.append(&power_heading);")); assert!(UI_LAYOUT_SRC.contains("power_buttons.set_homogeneous(true);")); assert!(UI_LAYOUT_SRC.contains("let audio_gain_scale =")); assert!(UI_LAYOUT_SRC.contains("audio_gain_scale.set_draw_value(false);")); assert!(UI_LAYOUT_SRC.contains("audio_gain_scale.set_size_request(96, -1);")); assert!(UI_LAYOUT_SRC.contains("let mic_gain_scale =")); assert!(UI_LAYOUT_SRC.contains("mic_gain_scale.set_draw_value(false);")); assert!(UI_LAYOUT_SRC.contains("mic_gain_scale.set_size_request(96, -1);")); assert!(!UI_LAYOUT_SRC.contains("audio_gain_row.set_size_request(220, -1);")); assert!(!UI_LAYOUT_SRC.contains("mic_gain_row.set_size_request(220, -1);")); assert!(!UI_LAYOUT_SRC.contains("power_shell.append(&audio_gain_row);")); assert!(!UI_LAYOUT_SRC.contains("power_shell.append(&mic_gain_row);")); assert_eq!( UI_LAYOUT_SRC .matches("connection_body.append(>k::Separator::new(gtk::Orientation::Horizontal));") .count(), 4, "recover/calibration/gpio/inputs/tools sections should remain visually separated" ); assert!( source_index("let power_heading = gtk::Label::new(Some(\"GPIO Power\"));") < source_index("let routing_heading = gtk::Label::new(Some(\"Inputs\"));") ); assert!( source_index("power_shell.append(&power_row);") < source_index("let routing_heading = gtk::Label::new(Some(\"Inputs\"));") ); assert!( source_index("let routing_heading = gtk::Label::new(Some(\"Inputs\"));") < source_index("let tools_heading = gtk::Label::new(Some(\"Tools\"));") ); assert!(UI_LAYOUT_SRC.contains("routing_buttons.set_homogeneous(true);")); }