gled-core 2.31.2

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
use rosc::{OscMessage, OscType};

use super::parse_message;
use crate::action::UiAction;
use crate::input::event::InputEvent;
use crate::input::osc::samples::sample_messages;
use crate::storage::asset::animation::argument::ArgumentKindId;
use crate::storage::asset::project::scene_instance_path::SceneInstanceUnion;

fn msg(addr: &str, args: Vec<OscType>) -> OscMessage {
    OscMessage {
        addr: addr.to_string(),
        args,
    }
}

#[test]
fn parses_project_main_dimmer() {
    let msg = msg("/project/main_dimmer", vec![OscType::Float(0.5)]);
    let action = parse_message(&msg).expect("main dimmer should parse");
    match action {
        UiAction::SetMainDimmer(value) => assert_eq!(value, 0.5),
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_scene_grid_opacity() {
    let msg = msg("/scene/grid/2/1/opacity", vec![OscType::Float(0.7)]);
    let action = parse_message(&msg).expect("grid opacity should parse");
    match action {
        UiAction::SetSceneOpacity(SceneInstanceUnion::Grid(location), value) => {
            assert_eq!(location.col, 2);
            assert_eq!(location.row, 1);
            assert_eq!(value, 0.7);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_scene_quick_active() {
    let msg = msg("/scene/quick/3/active", vec![OscType::Int(1)]);
    let action = parse_message(&msg).expect("quick active should parse");
    match action {
        UiAction::SetSceneActive(SceneInstanceUnion::Quick(index), value) => {
            assert_eq!(index.index, 3);
            assert!(value);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_selected_scene_toggle() {
    let msg = msg("/scene/selected/toggle", vec![]);
    let action = parse_message(&msg).expect("selected toggle should parse");
    assert!(matches!(
        action,
        UiAction::ToggleSceneActive(SceneInstanceUnion::Selected)
    ));
}

#[test]
fn parses_large_grid_location_without_static_bounds_check() {
    let msg = msg("/scene/grid/999/777/opacity", vec![OscType::Float(0.2)]);
    match parse_message(&msg).expect("large grid location should parse") {
        UiAction::SetSceneOpacity(SceneInstanceUnion::Grid(location), value) => {
            assert_eq!(location.col, 999);
            assert_eq!(location.row, 777);
            assert_eq!(value, 0.2);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_project_palette_none() {
    let msg = msg(
        "/project/palette",
        vec![OscType::String("none".to_string())],
    );
    let action = parse_message(&msg).expect("palette none should parse");
    match action {
        UiAction::SetProjectPaletteFromAsset(value) => assert!(value.is_none()),
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_scene_effect_config_f32() {
    let msg = msg(
        "/scene/grid/1/2/effect/0/config/f32/3",
        vec![OscType::Float(0.25)],
    );
    let action = parse_message(&msg).expect("effect config should parse");
    match action {
        UiAction::SetSceneEffectAnimationConfigF32(
            SceneInstanceUnion::Grid(location),
            effect_index,
            config_index,
            value,
        ) => {
            assert_eq!(location.col, 1);
            assert_eq!(location.row, 2);
            assert_eq!(effect_index, 0);
            assert_eq!(config_index, 3);
            assert_eq!(value, 0.25);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_selected_scene_effect_speed_exponent() {
    let msg = msg(
        "/scene/selected/effect/2/speed_exponent",
        vec![OscType::Int(-1)],
    );
    let action = parse_message(&msg).expect("selected effect speed should parse");
    match action {
        UiAction::SetSceneEffectSpeedExponent(
            SceneInstanceUnion::Selected,
            effect_index,
            value,
        ) => {
            assert_eq!(effect_index, 2);
            assert_eq!(value, -1);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_project_palette_gradient_edit() {
    let msg = msg(
        "/project/palette/gradient/4",
        vec![
            OscType::Float(0.1),
            OscType::Float(0.2),
            OscType::Float(0.3),
        ],
    );
    let action = parse_message(&msg).expect("palette gradient should parse");
    match action {
        UiAction::SetProjectPaletteGradient(index, rgb) => {
            assert_eq!(index, 4);
            assert_eq!(rgb, [0.1, 0.2, 0.3]);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_scene_effect_add() {
    let msg = msg("/scene/grid/3/1/effect/add", vec![]);
    let action = parse_message(&msg).expect("effect add should parse");
    match action {
        UiAction::AddSceneEffect(SceneInstanceUnion::Grid(location)) => {
            assert_eq!(location.col, 3);
            assert_eq!(location.row, 1);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_selected_scene_effect_delete() {
    let msg = msg("/scene/selected/effect/5/delete", vec![]);
    let action = parse_message(&msg).expect("effect delete should parse");
    match action {
        UiAction::RemoveSceneEffect(SceneInstanceUnion::Selected, index) => {
            assert_eq!(index, 5)
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_grid_activation_input_artnet() {
    let msg = msg(
        "/scene/grid/0/0/activation_input/artnet",
        vec![OscType::Int(77)],
    );
    let action = parse_message(&msg).expect("activation input should parse");
    match action {
        UiAction::SetSceneActivationInput(
            SceneInstanceUnion::Grid(location),
            Some(InputEvent::Artnet(channel)),
        ) => {
            assert_eq!(location.col, 0);
            assert_eq!(location.row, 0);
            assert_eq!(channel, 77);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_selected_palette_overwrite_none() {
    let msg = msg(
        "/scene/selected/palette_overwrite",
        vec![OscType::String("none".to_string())],
    );
    let action = parse_message(&msg).expect("palette overwrite should parse");
    match action {
        UiAction::SetScenePaletteOverwriteFromAsset(SceneInstanceUnion::Selected, value) => {
            assert!(matches!(value, Some(None)));
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_selected_palette_overwrite_primary_edit() {
    let msg = msg(
        "/scene/selected/palette_overwrite/primary",
        vec![
            OscType::Float(0.2),
            OscType::Float(0.4),
            OscType::Float(0.6),
        ],
    );
    let action = parse_message(&msg).expect("selected palette primary should parse");
    match action {
        UiAction::SetScenePaletteOverwritePrimary(SceneInstanceUnion::Selected, rgb) => {
            assert_eq!(rgb, [0.2, 0.4, 0.6]);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_grid_palette_overwrite_gradient_edit() {
    let msg = msg(
        "/scene/grid/4/2/palette_overwrite/gradient/3",
        vec![
            OscType::Float(0.3),
            OscType::Float(0.5),
            OscType::Float(0.7),
        ],
    );
    let action = parse_message(&msg).expect("grid palette gradient should parse");
    match action {
        UiAction::SetScenePaletteOverwriteGradient(
            SceneInstanceUnion::Grid(location),
            index,
            rgb,
        ) => {
            assert_eq!(location.col, 4);
            assert_eq!(location.row, 2);
            assert_eq!(index, 3);
            assert_eq!(rgb, [0.3, 0.5, 0.7]);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_project_artnet_control_universe() {
    let msg = msg("/project/artnet_control/universe", vec![OscType::Int(1337)]);
    let action = parse_message(&msg).expect("artnet control universe should parse");
    match action {
        UiAction::SetProjectArtnetControlUniverse(universe) => {
            assert_eq!(universe, 1337);
        }
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn parses_animation_argument_kind_edit() {
    let animation_id = "11111111-1111-1111-1111-111111111111";
    let msg = msg(
        &format!("/asset/animation/{animation_id}/argument/1/kind"),
        vec![OscType::String("degrees".to_string())],
    );
    let action = parse_message(&msg).expect("animation argument kind should parse");
    match action {
        UiAction::SetAnimationArgumentKind(_, argument_index, kind) => {
            assert_eq!(argument_index, 1);
            assert_eq!(kind, ArgumentKindId::Degrees);
        }
        _ => panic!("wrong action variant"),
    }
}

/// Groups were replaced by layouts: an effect now selects a layout by its
/// index in the stack, where 0 is the topology.
#[test]
fn parses_effect_layout_set() {
    let msg = msg("/scene/selected/effect/1/layout", vec![OscType::Int(2)]);
    let action = parse_message(&msg).expect("effect layout set should parse");
    match action {
        UiAction::SetSceneEffectLayout(SceneInstanceUnion::Selected, effect_index, layout) => {
            assert_eq!(effect_index, 1);
            assert_eq!(layout, Some(2));
        }
        _ => panic!("wrong action variant"),
    }
}

/// -1 lets the effect inherit its tile's layout again.
#[test]
fn parses_effect_layout_inherit() {
    let msg = msg("/scene/selected/effect/1/layout", vec![OscType::Int(-1)]);
    let action = parse_message(&msg).expect("effect layout inherit should parse");
    match action {
        UiAction::SetSceneEffectLayout(SceneInstanceUnion::Selected, effect_index, layout) => {
            assert_eq!(effect_index, 1);
            assert_eq!(layout, None);
        }
        _ => panic!("wrong action variant"),
    }
}

/// The group endpoints are gone with the concept.
#[test]
fn group_endpoints_are_rejected() {
    let msg = msg(
        "/project/groups/3",
        vec![OscType::String("wash".to_string())],
    );
    assert!(parse_message(&msg).is_err());
}

#[test]
fn parses_project_input_add_artnet() {
    let msg = msg("/project/input/tap/add/artnet", vec![OscType::Int(12)]);
    let action = parse_message(&msg).expect("project input add should parse");
    match action {
        UiAction::AddProjectTapInputArtnet(channel) => assert_eq!(channel, 12),
        _ => panic!("wrong action variant"),
    }
}

#[test]
fn every_sample_address_parses() {
    let failed: Vec<String> = sample_messages()
        .iter()
        // Subscriptions are handled before parsing.
        .filter(|message| !message.addr.starts_with("/osc/state/"))
        .filter(|message| parse_message(message).is_err())
        .map(|message| format!("{} {:?}", message.addr, message.args))
        .collect();
    assert!(failed.is_empty(), "not understood: {failed:#?}");
}