lesavka/testing/tests/client_launcher_layout_contract.rs

164 lines
7.4 KiB
Rust
Raw Normal View History

2026-04-21 17:55:26 -03:00
//! Contract tests for the launcher layout proportions.
//!
//! Scope: statically guard the GTK layout constants and sizing glue used by
//! the launcher shell.
//! Targets: `client/src/launcher/ui_components.rs`.
//! Why: the launcher is an operational control surface; accidental spacing
//! regressions can hide diagnostics or make eye/device previews unusable.
const UI_SRC: &str = include_str!("../../client/src/launcher/ui_components.rs");
fn const_i32(name: &str) -> i32 {
let needle = format!("const {name}: i32 = ");
let line = UI_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_SRC
.find(needle)
.unwrap_or_else(|| panic!("missing source marker: {needle}"))
}
2026-04-21 17:55:26 -03:00
#[test]
fn launcher_default_size_stays_inside_1080p() {
assert_eq!(const_i32("LAUNCHER_DEFAULT_WIDTH"), 1360);
assert_eq!(const_i32("LAUNCHER_DEFAULT_HEIGHT"), 820);
assert!(const_i32("LAUNCHER_DEFAULT_WIDTH") <= 1920);
assert!(const_i32("LAUNCHER_DEFAULT_HEIGHT") <= 1080);
}
#[test]
fn eye_panes_keep_the_locked_larger_preview_footprint() {
assert_eq!(const_i32("EYE_PREVIEW_MIN_WIDTH"), 460);
assert_eq!(const_i32("EYE_PREVIEW_MIN_HEIGHT"), 258);
assert!(
UI_SRC.contains("caption_label.set_halign(gtk::Align::End)")
|| UI_SRC.contains("capture_label.set_halign(gtk::Align::End)")
);
assert!(UI_SRC.contains("capture_label.set_ellipsize(pango::EllipsizeMode::Start);"));
2026-04-21 22:15:47 -03:00
assert!(!UI_SRC.contains("root.append(&stream_status);"));
assert!(UI_SRC.contains("stream_status.add_css_class(\"eye-inline-status\");"));
assert!(
source_index("controls_grid.attach(&breakout_row, 0, 1, 1, 1);")
< source_index("controls_grid.attach(&stream_status, 1, 1, 1, 1);")
);
assert!(
source_index("controls_grid.attach(&stream_status, 1, 1, 1, 1);")
< source_index("controls_grid.attach(&action_button, 2, 1, 1, 1);")
);
2026-04-21 17:55:26 -03:00
}
#[test]
fn device_staging_and_testing_bottoms_stay_locked_together() {
assert!(UI_SRC.contains("staging_row.set_homogeneous(true);"));
assert!(UI_SRC.contains("staging_row.set_vexpand(false);"));
2026-04-21 17:55:26 -03:00
assert!(UI_SRC.contains("devices_panel.set_valign(gtk::Align::Fill);"));
assert!(UI_SRC.contains("preview_panel.set_valign(gtk::Align::Fill);"));
assert!(UI_SRC.contains(
"let device_body_height_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Vertical);"
));
assert!(UI_SRC.contains("device_body_height_group.add_widget(&devices_body);"));
assert!(UI_SRC.contains("device_body_height_group.add_widget(&testing_row);"));
}
#[test]
fn device_testing_keeps_webcam_and_mic_playback_as_equal_bottom_columns() {
assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_WIDTH"), 280);
assert_eq!(const_i32("CAMERA_PREVIEW_VIEWPORT_HEIGHT"), 158);
assert!(UI_SRC.contains("webcam_group.set_valign(gtk::Align::Fill);"));
assert!(UI_SRC.contains("playback_group.set_valign(gtk::Align::Fill);"));
assert!(UI_SRC.contains("preview_body.set_vexpand(false);"));
2026-04-21 17:55:26 -03:00
assert!(UI_SRC.contains("playback_body.set_valign(gtk::Align::Fill);"));
assert!(UI_SRC.contains("audio_check_meter.set_vexpand(true);"));
assert!(UI_SRC.contains("playback_body.append(&audio_check_meter);"));
assert!(UI_SRC.contains("playback_body.append(&microphone_replay_button);"));
}
#[test]
fn operations_column_fills_height_and_splits_extra_space_between_logs() {
2026-04-21 22:15:47 -03:00
assert_eq!(const_i32("SIDE_LOG_MIN_HEIGHT"), 124);
2026-04-21 17:55:26 -03:00
assert!(UI_SRC.contains("operations.set_vexpand(true);"));
assert!(UI_SRC.contains("operations.set_valign(gtk::Align::Fill);"));
2026-04-21 22:15:47 -03:00
assert!(UI_SRC.contains("diagnostics_panel.set_vexpand(true);"));
assert!(UI_SRC.contains("console_panel.set_vexpand(true);"));
2026-04-21 17:55:26 -03:00
assert_eq!(
UI_SRC
2026-04-21 22:15:47 -03:00
.matches(".min_content_height(SIDE_LOG_MIN_HEIGHT)")
2026-04-21 17:55:26 -03:00
.count(),
2
);
assert_eq!(
2026-04-21 22:15:47 -03:00
UI_SRC.matches(".max_content_height(SIDE_LOG").count(),
0,
"the docked logs must be allowed to split extra right-rail height"
2026-04-21 17:55:26 -03:00
);
}
2026-04-21 20:19:47 -03:00
#[test]
fn relay_controls_keep_connect_inline_with_server_entry() {
assert!(UI_SRC.contains("build_panel(\"Relay Controls\")"));
assert!(UI_SRC.contains("let relay_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);"));
assert!(UI_SRC.contains("relay_row.append(&server_entry);"));
assert!(UI_SRC.contains("let start_button = gtk::Button::with_label(\"Connect\");"));
2026-04-21 22:15:47 -03:00
assert!(UI_SRC.contains("stabilize_button(&start_button, 108);"));
assert!(UI_SRC.contains("relay_row.append(&start_button);"));
assert!(
source_index("relay_row.append(&server_entry);")
< source_index("relay_row.append(&start_button);")
);
}
2026-04-21 20:19:47 -03:00
#[test]
fn remote_audio_gain_control_stays_in_the_operations_rail() {
assert!(!UI_SRC.contains("Remote Audio"));
assert!(UI_SRC.contains("let power_shell = gtk::Box::new(gtk::Orientation::Vertical, 6);"));
2026-04-22 00:56:03 -03:00
assert!(UI_SRC.contains("let channel_heading = gtk::Label::new(Some(\"Streams\"));"));
assert!(UI_SRC.contains("gtk::CheckButton::with_label(\"Webcam\")"));
assert!(UI_SRC.contains("gtk::CheckButton::with_label(\"Mic\")"));
assert!(UI_SRC.contains("gtk::CheckButton::with_label(\"Audio\")"));
assert!(!UI_SRC.contains("camera_combo.append(Some(\"auto\")"));
assert!(!UI_SRC.contains("speaker_combo.append(Some(\"auto\")"));
assert!(!UI_SRC.contains("microphone_combo.append(Some(\"auto\")"));
2026-04-21 22:15:47 -03:00
assert!(UI_SRC.contains("let power_heading = gtk::Label::new(Some(\"GPIO Power\"));"));
assert!(UI_SRC.contains("power_row.append(&power_heading);"));
2026-04-21 22:15:47 -03:00
assert!(UI_SRC.contains("power_buttons.set_homogeneous(true);"));
2026-04-21 20:19:47 -03:00
assert!(UI_SRC.contains("let audio_gain_scale ="));
assert!(UI_SRC.contains("audio_gain_scale.set_draw_value(false);"));
assert!(UI_SRC.contains("audio_gain_value.set_width_chars(5);"));
2026-04-22 00:56:03 -03:00
assert!(UI_SRC.contains("let mic_gain_scale ="));
assert!(UI_SRC.contains("mic_gain_scale.set_draw_value(false);"));
assert!(UI_SRC.contains("mic_gain_value.set_width_chars(5);"));
assert!(UI_SRC.contains("audio_gain_row.set_size_request(220, -1);"));
2026-04-22 00:56:03 -03:00
assert!(UI_SRC.contains("mic_gain_row.set_size_request(220, -1);"));
assert!(UI_SRC.contains("power_shell.append(&audio_gain_row);"));
2026-04-22 00:56:03 -03:00
assert!(UI_SRC.contains("power_shell.append(&mic_gain_row);"));
assert_eq!(
UI_SRC
.matches("connection_body.append(&gtk::Separator::new(gtk::Orientation::Horizontal));")
.count(),
2,
"the operations rail should not gain extra vertical sections that stretch the lower layout"
);
assert!(
2026-04-21 22:15:47 -03:00
source_index("let power_heading = gtk::Label::new(Some(\"GPIO Power\"));")
< source_index("let audio_gain_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);")
);
assert!(
source_index("power_shell.append(&audio_gain_row);")
2026-04-22 00:56:03 -03:00
< source_index("power_shell.append(&mic_gain_row);")
);
assert!(
source_index("power_shell.append(&mic_gain_row);")
2026-04-21 22:15:47 -03:00
< source_index("let routing_heading = gtk::Label::new(Some(\"Inputs\"));")
);
2026-04-21 22:15:47 -03:00
assert!(UI_SRC.contains("routing_buttons.set_homogeneous(true);"));
2026-04-21 20:19:47 -03:00
}