rmux 0.8.0

A local terminal multiplexer with a tmux-style CLI, daemon runtime, Rust SDK, and ratatui integration.
use ratatui::style::Color;
use rmux_proto::{CommandOutput, WebShareCreatedResponse, WebShareScope};

use super::qr;
use super::support::{url_label, LinkMode, OutputStyle, UrlLabel};
use super::{
    cards_fit_width, created_share_terminal_output, full_links_output,
    full_links_output_with_copy_fallback, render_created_share_with_style, should_stack_cards,
    ShareCard,
};

#[test]
fn created_output_includes_distinct_role_pins() {
    let created = WebShareCreatedResponse {
        share_id: "abc12345".to_owned(),
        scope: WebShareScope::Session("demo".parse().expect("session")),
        spectator_url: Some("https://share.rmux.io/#t=spectator".to_owned()),
        operator_url: Some("https://share.rmux.io/#t=operator".to_owned()),
        tunnel_provider: Some("NAME".to_owned()),
        tunnel_public_url: None,
        expires_at_unix: None,
        operator_pairing_code: Some("123456".to_owned()),
        spectator_pairing_code: Some("654321".to_owned()),
        max_spectators: Some(12),
        max_operators: Some(1),
        operator: true,
        spectator: true,
        controls: true,
        kill_session_on_expire: false,
        output: CommandOutput::from_stdout(Vec::new()),
    };

    let rendered = String::from_utf8(created_share_terminal_output(&created).stdout().to_vec())
        .expect("utf8 output");
    let visible = strip_ansi(&rendered);

    assert!(visible.contains("123456"));
    assert!(visible.contains("654321"));
    assert!(!visible.contains("123 456"));
    assert!(!visible.contains("654 321"));
    assert!(visible.contains("share.rmux.io is static"));
}

#[test]
fn plain_terminal_urls_that_do_not_fit_are_printed_below() {
    let url =
        "https://share.rmux.io/#t=abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz";
    let card = ShareCard {
        title: "SPECTATOR",
        subtitle: "read-only view",
        color: Color::LightBlue,
        url,
        pin: Some("654321"),
        limit: None,
    };

    assert_eq!(
        url_label(url, 40, LinkMode::PlainUrl),
        UrlLabel::PrintedBelow
    );
    let links = full_links_output(52, &[card], LinkMode::PlainUrl);

    assert!(links.contains("Full web-share URLs:"));
    assert!(links.contains(url));
    assert!(!links.contains(''));
}

#[test]
fn osc8_compact_urls_are_printed_below_as_copy_fallback() {
    let url = "https://share.rmux.io/#e=wss%3A%2F%2Ftail.example%2Fws&t=abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz";
    let card = ShareCard {
        title: "SPECTATOR",
        subtitle: "read-only view",
        color: Color::LightBlue,
        url,
        pin: None,
        limit: None,
    };

    let links = full_links_output(52, &[card], LinkMode::Osc8);

    assert!(links.contains("Full web-share URLs:"));
    assert!(links.contains(url));
    assert!(!links.contains(''));
}

#[test]
fn terminal_fallback_prints_raw_links_even_when_card_urls_fit() {
    let url = "https://share.rmux.io/#t=abcdefghijklmnopqrstuvwxyz0123456789";
    let card = ShareCard {
        title: "SPECTATOR",
        subtitle: "read-only view",
        color: Color::LightBlue,
        url,
        pin: None,
        limit: None,
    };

    assert!(full_links_output_with_copy_fallback(
        120,
        std::slice::from_ref(&card),
        LinkMode::PlainUrl,
        false
    )
    .is_empty());

    let links = full_links_output_with_copy_fallback(120, &[card], LinkMode::PlainUrl, true);
    assert!(links.contains("Full web-share URLs:"));
    assert!(links.contains(url));
}

#[test]
fn plain_output_contains_no_ansi_and_keeps_scannable_qr() {
    let created = WebShareCreatedResponse {
        share_id: "abc12345".to_owned(),
        scope: WebShareScope::Session("demo".parse().expect("session")),
        spectator_url: Some("https://share.rmux.io/#t=spectator".to_owned()),
        operator_url: Some("https://share.rmux.io/#t=operator".to_owned()),
        tunnel_provider: Some("NAME".to_owned()),
        tunnel_public_url: None,
        expires_at_unix: None,
        operator_pairing_code: Some("123456".to_owned()),
        spectator_pairing_code: Some("654321".to_owned()),
        max_spectators: None,
        max_operators: None,
        operator: true,
        spectator: true,
        controls: true,
        kill_session_on_expire: false,
        output: CommandOutput::from_stdout(Vec::new()),
    };

    let rendered =
        render_created_share_with_style(&created, OutputStyle::Plain).expect("plain render");

    assert!(!rendered.contains('\x1b'));
    assert!(rendered.contains(''));
    assert!(rendered.contains("operator: https://share.rmux.io/#t=operator"));
    assert!(rendered.contains("spectator: https://share.rmux.io/#t=spectator"));
}

#[test]
fn plain_long_urls_do_not_force_stacked_cards_on_wide_terminals() {
    let operator_url = "https://share.rmux.io/#t=abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJK";
    let spectator_url = "https://share.rmux.io/#t=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJK";
    let cards = [
        ShareCard {
            title: "OPERATOR",
            subtitle: "control + type",
            color: Color::LightRed,
            url: operator_url,
            pin: None,
            limit: None,
        },
        ShareCard {
            title: "SPECTATOR",
            subtitle: "read-only view",
            color: Color::LightBlue,
            url: spectator_url,
            pin: None,
            limit: None,
        },
    ];

    assert!(!should_stack_cards(108, &cards, LinkMode::PlainUrl));
}

#[test]
fn narrow_single_card_rejects_truncated_qr_width() {
    let url = "https://share.rmux.io/#e=wss%3A%2F%2Ftunnel.example%2Fws&t=abcdefghijklmnopqrstuvwxyz0123456789";
    let card = ShareCard {
        title: "SPECTATOR",
        subtitle: "read-only view",
        color: Color::LightBlue,
        url,
        pin: None,
        limit: None,
    };

    assert!(!cards_fit_width(
        44,
        std::slice::from_ref(&card),
        qr::RenderMode::Compact
    ));
    assert!(cards_fit_width(54, &[card], qr::RenderMode::Compact));
}

#[test]
fn created_output_uses_custom_frontend_host() {
    let created = WebShareCreatedResponse {
        share_id: "abc12345".to_owned(),
        scope: WebShareScope::Session("demo".parse().expect("session")),
        spectator_url: Some("https://share.fork.example/share/#t=spectator".to_owned()),
        operator_url: None,
        tunnel_provider: None,
        tunnel_public_url: None,
        expires_at_unix: None,
        operator_pairing_code: None,
        spectator_pairing_code: Some("654321".to_owned()),
        max_spectators: None,
        max_operators: None,
        operator: false,
        spectator: true,
        controls: false,
        kill_session_on_expire: false,
        output: CommandOutput::from_stdout(Vec::new()),
    };

    let rendered = String::from_utf8(created_share_terminal_output(&created).stdout().to_vec())
        .expect("utf8 output");
    let visible = strip_ansi(&rendered);

    assert!(visible.contains("share.fork.example is static"));
    assert!(!visible.contains("share.rmux.io is static"));
}

fn strip_ansi(input: &str) -> String {
    let mut output = String::new();
    let mut chars = input.chars().peekable();
    while let Some(ch) = chars.next() {
        if ch != '\x1b' {
            output.push(ch);
            continue;
        }
        match chars.peek().copied() {
            Some(']') => {
                chars.next();
                while let Some(ch) = chars.next() {
                    if ch == '\x07' {
                        break;
                    }
                    if ch == '\x1b' && chars.next_if_eq(&'\\').is_some() {
                        break;
                    }
                }
            }
            Some('[') => {
                chars.next();
                for ch in chars.by_ref() {
                    if ('@'..='~').contains(&ch) {
                        break;
                    }
                }
            }
            _ => {}
        }
    }
    output
}