bubbles-dialogue 1.0.0

Lightweight engine-agnostic dialogue runtime for Rust games.
Documentation
//! Unit tests for [`super::FunctionLibrary`].

mod plural;
mod select;

use crate::error::DialogueError;

use super::*;

#[test]
fn default_library_matches_new() {
    assert_eq!(
        FunctionLibrary::new()
            .call("round", vec![Value::Number(2.3)])
            .unwrap(),
        FunctionLibrary::default()
            .call("round", vec![Value::Number(2.3)])
            .unwrap(),
    );
}

#[test]
fn round_builtin() {
    let lib = FunctionLibrary::new();
    assert_eq!(
        lib.call("round", vec![Value::Number(3.7)]).unwrap(),
        Value::Number(4.0)
    );
}

#[test]
fn min_max_builtins() {
    let lib = FunctionLibrary::new();
    assert_eq!(
        lib.call("min", vec![Value::Number(2.0), Value::Number(5.0)])
            .unwrap(),
        Value::Number(2.0)
    );
    assert_eq!(
        lib.call("max", vec![Value::Number(2.0), Value::Number(5.0)])
            .unwrap(),
        Value::Number(5.0)
    );
}

#[test]
fn unknown_function_errors() {
    let lib = FunctionLibrary::new();
    assert!(lib.call("does_not_exist", vec![]).is_err());
}

#[test]
fn custom_function_registered() {
    let mut lib = FunctionLibrary::new();
    lib.register("double", |args| {
        if let [Value::Number(n)] = args.as_slice() {
            Ok(Value::Number(n * 2.0))
        } else {
            Err(DialogueError::Function {
                name: "double".into(),
                message: "expected one number".into(),
            })
        }
    });
    assert_eq!(
        lib.call("double", vec![Value::Number(5.0)]).unwrap(),
        Value::Number(10.0)
    );
}

#[cfg(feature = "rand")]
#[test]
fn random_range_within_bounds() {
    let lib = FunctionLibrary::new();
    for _ in 0..20 {
        let v = lib
            .call("random_range", vec![Value::Number(1.0), Value::Number(6.0)])
            .unwrap();
        if let Value::Number(n) = v {
            assert!((1.0..=6.0).contains(&n));
        }
    }
}

#[test]
fn floor_ceil_abs() {
    let lib = FunctionLibrary::new();
    assert_eq!(
        lib.call("floor", vec![Value::Number(2.9)]).unwrap(),
        Value::Number(2.0)
    );
    assert_eq!(
        lib.call("ceil", vec![Value::Number(2.1)]).unwrap(),
        Value::Number(3.0)
    );
    assert_eq!(
        lib.call("abs", vec![Value::Number(-7.5)]).unwrap(),
        Value::Number(7.5)
    );
}

#[test]
fn clamp_three_numbers() {
    let lib = FunctionLibrary::new();
    assert_eq!(
        lib.call(
            "clamp",
            vec![Value::Number(10.0), Value::Number(0.0), Value::Number(5.0)]
        )
        .unwrap(),
        Value::Number(5.0)
    );
}

#[test]
fn clamp_wrong_arity_errors() {
    let lib = FunctionLibrary::new();
    assert!(
        lib.call("clamp", vec![Value::Number(1.0), Value::Number(2.0)])
            .is_err()
    );
}

#[test]
fn string_converts_value() {
    let lib = FunctionLibrary::new();
    assert_eq!(
        lib.call("string", vec![Value::Number(42.0)]).unwrap(),
        Value::Text("42".into())
    );
}

#[test]
fn string_no_args_errors() {
    let lib = FunctionLibrary::new();
    assert!(lib.call("string", vec![]).is_err());
}

#[test]
fn int_truncates() {
    let lib = FunctionLibrary::new();
    assert_eq!(
        lib.call("int", vec![Value::Number(-3.7)]).unwrap(),
        Value::Number(-3.0)
    );
}

#[test]
fn round_wrong_args_errors() {
    let lib = FunctionLibrary::new();
    assert!(lib.call("round", vec![]).is_err());
    assert!(
        lib.call("round", vec![Value::Number(1.0), Value::Number(2.0)])
            .is_err()
    );
}

#[test]
fn min_wrong_args_errors() {
    let lib = FunctionLibrary::new();
    assert!(lib.call("min", vec![Value::Number(1.0)]).is_err());
}

#[test]
fn max_wrong_args_errors() {
    let lib = FunctionLibrary::new();
    assert!(lib.call("max", vec![Value::Text("a".into())]).is_err());
}

#[test]
fn floor_ceil_abs_int_require_numbers() {
    let lib = FunctionLibrary::new();
    assert!(lib.call("floor", vec![Value::Text("nope".into())]).is_err());
    assert!(lib.call("ceil", vec![]).is_err());
    assert!(lib.call("abs", vec![Value::Bool(true)]).is_err());
    assert!(lib.call("int", vec![Value::Text("1".into())]).is_err());
}

#[cfg(feature = "rand")]
#[test]
fn random_range_lo_gt_hi_errors() {
    let lib = FunctionLibrary::new();
    assert!(
        lib.call("random_range", vec![Value::Number(6.0), Value::Number(1.0)])
            .is_err()
    );
}

#[cfg(feature = "rand")]
#[test]
fn random_range_fractional_arg_errors() {
    let lib = FunctionLibrary::new();
    assert!(
        lib.call("random_range", vec![Value::Number(1.5), Value::Number(6.0)])
            .is_err()
    );
}

#[cfg(feature = "rand")]
#[test]
fn dice_negative_sides_errors() {
    let lib = FunctionLibrary::new();
    assert!(
        lib.call("dice", vec![Value::Number(-1.0), Value::Number(3.0)])
            .is_err()
    );
}

#[cfg(feature = "rand")]
#[test]
fn dice_fractional_count_errors() {
    let lib = FunctionLibrary::new();
    assert!(
        lib.call("dice", vec![Value::Number(6.0), Value::Number(2.5)])
            .is_err()
    );
}

#[cfg(feature = "rand")]
#[test]
fn dice_zero_sides_errors() {
    let lib = FunctionLibrary::new();
    assert!(
        lib.call("dice", vec![Value::Number(0.0), Value::Number(2.0)])
            .is_err()
    );
}

#[cfg(feature = "rand")]
#[test]
fn dice_rolls_sum() {
    let lib = FunctionLibrary::new();
    let v = lib
        .call("dice", vec![Value::Number(6.0), Value::Number(3.0)])
        .unwrap();
    if let Value::Number(n) = v {
        assert!((3.0..=18.0).contains(&n));
    } else {
        panic!("expected number");
    }
}

#[cfg(feature = "rand")]
#[test]
fn random_builtin_returns_unit_intervalish() {
    let lib = FunctionLibrary::new();
    let Value::Number(n) = lib.call("random", vec![]).unwrap() else {
        panic!("expected number");
    };
    assert!((0.0..=1.0).contains(&n));
}