use crate::{
types::{
Account, Date, Decimal, Duration, Float32 as F32, Float64 as F64, Int, Int128, Nat, Nat128,
Principal, Subaccount, Timestamp, Ulid,
},
value::{
CoercionFamily, CoercionFamilyExt, SchemaInvariantError, TextMode, Value, ValueEnum,
hash_value,
},
};
use std::cmp::Ordering;
fn v_f64(x: f64) -> Value {
Value::Float64(F64::try_new(x).expect("finite f64"))
}
fn v_f32(x: f32) -> Value {
Value::Float32(F32::try_new(x).expect("finite f32"))
}
fn v_i(x: i64) -> Value {
Value::Int(x)
}
fn v_u(x: u64) -> Value {
Value::Uint(x)
}
fn v_d_i(x: i64) -> Value {
Value::Decimal(Decimal::from_i64(x).unwrap())
}
fn v_txt(s: &str) -> Value {
Value::Text(s.to_string())
}
macro_rules! sample_value_for_scalar {
(Account) => {
Value::Account(Account::dummy(7))
};
(Blob) => {
Value::Blob(vec![1u8, 2u8, 3u8])
};
(Bool) => {
Value::Bool(true)
};
(Date) => {
Value::Date(Date::new(2024, 1, 2))
};
(Decimal) => {
Value::Decimal(Decimal::new(123, 2))
};
(Duration) => {
Value::Duration(Duration::from_secs(1))
};
(Enum) => {
Value::Enum(ValueEnum::loose("example"))
};
(Float32) => {
Value::Float32(F32::try_new(1.25).expect("Float32 sample should be finite"))
};
(Float64) => {
Value::Float64(F64::try_new(2.5).expect("Float64 sample should be finite"))
};
(Int) => {
Value::Int(-7)
};
(Int128) => {
Value::Int128(Int128::from(123i128))
};
(IntBig) => {
Value::IntBig(Int::from(99i32))
};
(Principal) => {
Value::Principal(Principal::from_slice(&[1u8, 2u8, 3u8]))
};
(Subaccount) => {
Value::Subaccount(Subaccount::new([1u8; 32]))
};
(Text) => {
Value::Text("example".to_string())
};
(Timestamp) => {
Value::Timestamp(Timestamp::from_secs(1))
};
(Uint) => {
Value::Uint(7)
};
(Uint128) => {
Value::Uint128(Nat128::from(9u128))
};
(UintBig) => {
Value::UintBig(Nat::from(11u64))
};
(Ulid) => {
Value::Ulid(Ulid::from_u128(42))
};
(Unit) => {
Value::Unit
};
}
fn registry_numeric_cases() -> Vec<(Value, bool)> {
macro_rules! collect_cases {
( @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
vec![ $( (sample_value_for_scalar!($scalar), $is_numeric) ),* ]
};
( @args $($ignore:tt)*; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
vec![ $( (sample_value_for_scalar!($scalar), $is_numeric) ),* ]
};
}
let cases = scalar_registry!(collect_cases);
cases
}
fn registry_numeric_coercion_cases() -> Vec<(Value, bool)> {
macro_rules! collect_cases {
( @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
vec![ $( (sample_value_for_scalar!($scalar), $supports_numeric_coercion) ),* ]
};
( @args $($ignore:tt)*; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
vec![ $( (sample_value_for_scalar!($scalar), $supports_numeric_coercion) ),* ]
};
}
scalar_registry!(collect_cases)
}
fn registry_keyable_cases() -> Vec<(Value, bool)> {
macro_rules! collect_cases {
( @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
vec![ $( (sample_value_for_scalar!($scalar), $is_keyable) ),* ]
};
( @args $($ignore:tt)*; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
vec![ $( (sample_value_for_scalar!($scalar), $is_keyable) ),* ]
};
}
let cases = scalar_registry!(collect_cases);
cases
}
fn registry_coercion_family_cases() -> Vec<(Value, CoercionFamily)> {
macro_rules! collect_cases {
( @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
vec![ $( (sample_value_for_scalar!($scalar), $coercion_family) ),* ]
};
( @args $($ignore:tt)*; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
vec![ $( (sample_value_for_scalar!($scalar), $coercion_family) ),* ]
};
}
let cases = scalar_registry!(collect_cases);
cases
}
#[test]
fn as_storage_key_some_for_keyable_variants() {
assert!(Value::Int(7).as_storage_key().is_some());
assert!(Value::Uint(7).as_storage_key().is_some());
assert!(Value::Ulid(Ulid::MIN).as_storage_key().is_some());
assert!(Value::Unit.as_storage_key().is_some());
assert!(v_txt("x").as_storage_key().is_none());
assert!(
Value::Decimal(Decimal::new(1, 0))
.as_storage_key()
.is_none()
);
assert!(Value::List(vec![]).as_storage_key().is_none());
assert!(Value::Null.as_storage_key().is_none());
}
#[test]
fn storage_key_round_trips_through_value() {
let values = [
Value::Int(-9),
Value::Uint(9),
Value::Ulid(Ulid::MAX),
Value::Unit,
];
for v in values {
let key = v
.as_storage_key()
.expect("value should be convertible to storage key");
let back = key.as_value();
assert_eq!(
v, back,
"Value <-> StorageKey round trip failed: {v:?} -> {key:?} -> {back:?}"
);
}
}
#[test]
fn canonical_tag_and_rank_are_stable() {
let map = Value::Map(vec![]);
let list = Value::List(vec![]);
let cases = vec![
(Value::Account(Account::dummy(7)), 1u8),
(Value::Blob(vec![1u8]), 2),
(Value::Bool(true), 3),
(Value::Date(Date::new(2024, 1, 2)), 4),
(Value::Decimal(Decimal::new(123, 2)), 5),
(Value::Duration(Duration::from_secs(1)), 6),
(Value::Enum(ValueEnum::loose("example")), 7),
(
Value::Float32(F32::try_new(1.25).expect("Float32 sample should be finite")),
8,
),
(
Value::Float64(F64::try_new(2.5).expect("Float64 sample should be finite")),
9,
),
(Value::Int(-7), 10),
(Value::Int128(Int128::from(123i128)), 11),
(Value::IntBig(Int::from(99i32)), 12),
(list, 13),
(map, 14),
(Value::Null, 15),
(
Value::Principal(Principal::from_slice(&[1u8, 2u8, 3u8])),
16,
),
(Value::Subaccount(Subaccount::new([1u8; 32])), 17),
(Value::Text("example".to_string()), 18),
(Value::Timestamp(Timestamp::from_secs(1)), 19),
(Value::Uint(7), 20),
(Value::Uint128(Nat128::from(9u128)), 21),
(Value::UintBig(Nat::from(11u64)), 22),
(Value::Ulid(Ulid::from_u128(42)), 23),
(Value::Unit, 24),
];
for (value, expected_tag) in cases {
assert_eq!(
value.canonical_tag().to_u8(),
expected_tag,
"value: {value:?}"
);
assert_eq!(value.canonical_rank(), expected_tag - 1, "value: {value:?}");
}
}
#[test]
fn value_is_numeric_matches_registry_flag() {
for (value, expected) in registry_numeric_cases() {
assert_eq!(value.is_numeric(), expected, "value: {value:?}");
}
}
#[test]
fn value_supports_numeric_coercion_matches_registry_flag() {
for (value, expected) in registry_numeric_coercion_cases() {
assert_eq!(
value.supports_numeric_coercion(),
expected,
"value: {value:?}"
);
}
}
#[test]
fn value_as_storage_key_matches_registry_flag() {
for (value, is_keyable) in registry_keyable_cases() {
assert_eq!(
value.as_storage_key().is_some(),
is_keyable,
"value: {value:?}"
);
}
}
#[test]
fn value_coercion_family_matches_registry_flag() {
for (value, expected_coercion_family) in registry_coercion_family_cases() {
assert_eq!(
value.coercion_family(),
expected_coercion_family,
"value: {value:?}"
);
}
}
#[test]
fn coercion_family_surface_is_stable_for_core_variants() {
assert_eq!(Value::Int(1).coercion_family(), CoercionFamily::Numeric);
assert_eq!(
Value::Decimal(Decimal::new(5, 1)).coercion_family(),
CoercionFamily::Numeric
);
assert_eq!(
Value::Text("x".to_string()).coercion_family(),
CoercionFamily::Textual
);
assert_eq!(
Value::Principal(Principal::from_slice(&[1u8, 2u8])).coercion_family(),
CoercionFamily::Identifier
);
assert_eq!(
Value::Enum(ValueEnum::loose("V")).coercion_family(),
CoercionFamily::Enum
);
assert_eq!(
Value::Blob(vec![1u8, 2u8]).coercion_family(),
CoercionFamily::Blob
);
assert_eq!(Value::Bool(true).coercion_family(), CoercionFamily::Bool);
assert_eq!(
Value::List(vec![Value::Int(1)]).coercion_family(),
CoercionFamily::Collection
);
assert_eq!(
Value::from_map(vec![(Value::Text("k".to_string()), Value::Int(1))])
.expect("map")
.coercion_family(),
CoercionFamily::Collection
);
assert_eq!(Value::Null.coercion_family(), CoercionFamily::Null);
assert_eq!(Value::Unit.coercion_family(), CoercionFamily::Unit);
}
#[test]
fn value_unit_has_unit_coercion_family() {
assert_eq!(Value::Unit.coercion_family(), CoercionFamily::Unit);
}
#[test]
fn cmp_numeric_int_nat_eq_and_order() {
assert_eq!(v_i(10).cmp_numeric(&v_u(10)), Some(Ordering::Equal));
assert_eq!(v_i(9).cmp_numeric(&v_u(10)), Some(Ordering::Less));
assert_eq!(v_i(-1).cmp_numeric(&v_u(0)), Some(Ordering::Less));
}
#[test]
fn cmp_numeric_int_float_eq() {
assert_eq!(v_i(42).cmp_numeric(&v_f64(42.0)), Some(Ordering::Equal));
assert_eq!(v_i(42).cmp_numeric(&v_f32(42.0)), Some(Ordering::Equal));
}
#[test]
fn cmp_numeric_decimal_int_and_float() {
assert_eq!(v_d_i(10).cmp_numeric(&v_i(10)), Some(Ordering::Equal));
assert_eq!(v_d_i(10).cmp_numeric(&v_f64(10.0)), Some(Ordering::Equal));
assert_eq!(v_d_i(11).cmp_numeric(&v_f64(10.5)), Some(Ordering::Greater));
}
#[test]
#[expect(clippy::cast_precision_loss)]
fn cmp_numeric_safe_int_boundary() {
let safe: i64 = 9_007_199_254_740_992; let int_safe = v_i(safe);
let float_safe = v_f64(safe as f64);
assert_eq!(int_safe.cmp_numeric(&float_safe), Some(Ordering::Equal));
let int_unsafe = v_i(safe + 1);
assert_eq!(int_unsafe.cmp_numeric(&float_safe), Some(Ordering::Greater));
}
#[test]
fn cmp_numeric_neg_zero_equals_zero() {
let neg_zero = Value::Float64(F64::try_new(-0.0).unwrap());
assert_eq!(neg_zero.cmp_numeric(&v_i(0)), Some(Ordering::Equal));
let neg_zero32 = Value::Float32(F32::try_new(-0.0).unwrap());
assert_eq!(neg_zero32.cmp_numeric(&v_i(0)), Some(Ordering::Equal));
}
#[test]
fn cmp_numeric_respects_registry_numeric_coercion_flag() {
for (value, supports_numeric_coercion) in registry_numeric_coercion_cases() {
let cmp = value.cmp_numeric(&value);
if supports_numeric_coercion {
assert_eq!(cmp, Some(Ordering::Equal), "value: {value:?}");
} else {
assert!(cmp.is_none(), "value: {value:?}");
}
}
}
#[test]
fn cmp_numeric_rejects_date_and_bigints() {
let date = Value::Date(Date::new(2024, 1, 2));
let int_big = Value::IntBig(Int::from(10i32));
let uint_big = Value::UintBig(Nat::from(10u64));
let one = Value::Int(1);
assert!(!date.supports_numeric_coercion());
assert!(!int_big.supports_numeric_coercion());
assert!(!uint_big.supports_numeric_coercion());
assert!(date.cmp_numeric(&one).is_none());
assert!(int_big.cmp_numeric(&one).is_none());
assert!(uint_big.cmp_numeric(&one).is_none());
}
#[test]
fn cmp_numeric_is_unreachable_for_non_numeric_coercible_values() {
let left = Value::Date(Date::EPOCH);
let right = Value::Int(0);
assert!(!left.supports_numeric_coercion());
assert!(left.cmp_numeric(&right).is_none());
assert!(left.partial_cmp(&right).is_none());
}
#[test]
fn partial_ord_cross_variant_is_none() {
assert!(v_i(1).partial_cmp(&v_f64(1.0)).is_none());
assert!(v_txt("a").partial_cmp(&v_txt("b")).is_some());
}
#[test]
fn from_map_is_canonical_and_order_independent() {
let map_a = Value::from_map(vec![
(v_txt("c"), v_u(3)),
(v_txt("a"), v_u(1)),
(v_txt("b"), v_u(2)),
])
.expect("map_a should normalize");
let map_b = Value::from_map(vec![
(v_txt("a"), v_u(1)),
(v_txt("b"), v_u(2)),
(v_txt("c"), v_u(3)),
])
.expect("map_b should normalize");
assert_eq!(map_a, map_b);
let hash_a = hash_value(&map_a).expect("hash map_a");
let hash_b = hash_value(&map_b).expect("hash map_b");
assert_eq!(hash_a, hash_b);
}
#[test]
fn try_from_map_vec_is_canonical_and_order_independent() {
let map_a = Value::try_from(vec![
(v_txt("c"), v_u(3)),
(v_txt("a"), v_u(1)),
(v_txt("b"), v_u(2)),
])
.expect("map_a should normalize");
let map_b = Value::try_from(vec![
(v_txt("a"), v_u(1)),
(v_txt("b"), v_u(2)),
(v_txt("c"), v_u(3)),
])
.expect("map_b should normalize");
assert_eq!(map_a, map_b);
}
#[test]
fn try_from_map_vec_returns_schema_invariant_error() {
let err = Value::try_from(vec![(v_txt("a"), v_u(1)), (v_txt("a"), v_u(2))])
.expect_err("duplicate map keys should fail");
assert!(matches!(
err,
SchemaInvariantError::InvalidMapValue(crate::value::MapValueError::DuplicateKey { .. })
));
}
#[test]
fn canonical_cmp_key_is_total_for_enum_payloads() {
let left = Value::Enum(
ValueEnum::new("Any", Some("test::Enum")).with_payload(Value::from_slice(&[v_i(1)])),
);
let right = Value::Enum(
ValueEnum::new("Any", Some("test::Enum"))
.with_payload(Value::from_map(vec![(v_txt("k"), v_i(1))]).expect("map payload")),
);
let forward = Value::canonical_cmp_key(&left, &right);
let reverse = Value::canonical_cmp_key(&right, &left);
assert_eq!(forward, reverse.reverse());
assert_ne!(forward, Ordering::Equal);
}
#[test]
fn canonical_cmp_mixed_variant_follows_rank() {
let low = Value::Account(Account::dummy(1));
let high = Value::Text("x".to_string());
assert_eq!(Value::canonical_cmp(&low, &high), Ordering::Less);
assert_eq!(Value::canonical_cmp(&high, &low), Ordering::Greater);
}
#[test]
fn canonical_cmp_key_matches_canonical_cmp() {
let left = Value::from_map(vec![(v_txt("a"), v_i(1))]).expect("map");
let right = Value::from_map(vec![(v_txt("a"), v_i(2))]).expect("map");
assert_eq!(
Value::canonical_cmp_key(&left, &right),
Value::canonical_cmp(&left, &right),
);
}
#[test]
fn canonical_cmp_map_entry_orders_by_key_then_value() {
assert_eq!(
Value::canonical_cmp_map_entry(&v_txt("a"), &v_u(9), &v_txt("b"), &v_u(1)),
Ordering::Less,
"map entry comparison must prioritize canonical key ordering",
);
assert_eq!(
Value::canonical_cmp_map_entry(&v_txt("k"), &v_u(1), &v_txt("k"), &v_u(2)),
Ordering::Less,
"map entry comparison must use canonical value ordering when keys tie",
);
assert_eq!(
Value::canonical_cmp_map_entry(&v_txt("k"), &v_u(2), &v_txt("k"), &v_u(1)),
Ordering::Greater,
"map entry comparison must keep value tie-break deterministic",
);
}
#[test]
fn list_contains_scalar() {
let l = Value::from_slice(&[v_i(1), v_txt("a")]);
assert_eq!(l.contains(&v_i(1)), Some(true));
assert_eq!(l.contains(&v_i(2)), Some(false));
}
#[test]
fn list_contains_any_all_and_vacuous_truth() {
let l = Value::from_slice(&[v_txt("x"), v_txt("y")]);
let needles_any = Value::from_slice(&[v_txt("z"), v_txt("y")]);
let needles_all = Value::from_slice(&[v_txt("x"), v_txt("y")]);
let empty = Value::from_slice::<Value>(&[]);
assert_eq!(l.contains_any(&needles_any), Some(true));
assert_eq!(l.contains_all(&needles_all), Some(true));
assert_eq!(l.contains_any(&empty), Some(false), "AnyIn([]) == false");
assert_eq!(l.contains_all(&empty), Some(true), "AllIn([]) == true");
}
#[test]
fn contains_any_list_vs_list() {
let haystack = Value::from_slice(&[v_i(1), v_i(2), v_i(3)]);
let needles = Value::from_slice(&[v_i(4), v_i(2)]);
assert_eq!(haystack.contains_any(&needles), Some(true));
let needles_none = Value::from_slice(&[v_i(4), v_i(5)]);
assert_eq!(haystack.contains_any(&needles_none), Some(false));
let empty = Value::from_slice::<Value>(&[]);
assert_eq!(
haystack.contains_any(&empty),
Some(false),
"AnyIn([]) == false"
);
}
#[test]
fn contains_all_list_vs_list() {
let haystack = Value::from_slice(&[v_txt("a"), v_txt("b"), v_txt("c")]);
let needles = Value::from_slice(&[v_txt("a"), v_txt("c")]);
assert_eq!(haystack.contains_all(&needles), Some(true));
let needles_missing = Value::from_slice(&[v_txt("a"), v_txt("z")]);
assert_eq!(haystack.contains_all(&needles_missing), Some(false));
let empty = Value::from_slice::<Value>(&[]);
assert_eq!(
haystack.contains_all(&empty),
Some(true),
"AllIn([]) == true"
);
}
#[test]
fn contains_any_list_vs_scalar() {
let haystack = Value::from_slice(&[v_i(10), v_i(20)]);
assert_eq!(haystack.contains_any(&v_i(20)), Some(true));
assert_eq!(haystack.contains_any(&v_i(99)), Some(false));
}
#[test]
fn contains_all_list_vs_scalar() {
let haystack = Value::from_slice(&[v_i(10), v_i(20)]);
assert_eq!(haystack.contains_all(&v_i(20)), Some(true));
assert_eq!(haystack.contains_all(&v_i(99)), Some(false));
}
#[test]
fn contains_any_scalar_vs_list() {
let scalar = v_txt("hello");
let needles_yes = Value::from_slice(&[v_txt("x"), v_txt("hello")]);
let needles_no = Value::from_slice(&[v_txt("x"), v_txt("y")]);
assert_eq!(scalar.contains_any(&needles_yes), Some(true));
assert_eq!(scalar.contains_any(&needles_no), Some(false));
}
#[test]
fn contains_all_scalar_vs_list() {
let scalar = v_txt("hello");
let needles_yes = Value::from_slice(&[v_txt("hello")]);
let needles_extra = Value::from_slice(&[v_txt("hello"), v_txt("world")]);
let empty = Value::from_slice::<Value>(&[]);
assert_eq!(scalar.contains_all(&needles_yes), Some(true));
assert_eq!(scalar.contains_all(&needles_extra), Some(false));
assert_eq!(
scalar.contains_all(&empty),
Some(false),
"Scalar all-in empty should be false"
);
}
#[test]
fn contains_any_scalar_vs_scalar() {
let scalar = v_u(5);
assert_eq!(scalar.contains_any(&v_u(5)), Some(true));
assert_eq!(scalar.contains_any(&v_u(6)), Some(false));
}
#[test]
fn contains_all_scalar_vs_scalar() {
let scalar = v_u(5);
assert_eq!(scalar.contains_all(&v_u(5)), Some(true));
assert_eq!(scalar.contains_all(&v_u(6)), Some(false));
}
#[test]
fn in_list_ci_text_vs_list() {
let haystack = Value::from_slice(&[v_txt("Alpha"), v_txt("Beta")]);
assert_eq!(v_txt("alpha").in_list_ci(&haystack), Some(true));
assert_eq!(v_txt("BETA").in_list_ci(&haystack), Some(true));
assert_eq!(v_txt("gamma").in_list_ci(&haystack), Some(false));
}
#[test]
fn list_contains_ci_scalar() {
let list = Value::from_slice(&[v_txt("Foo"), v_txt("Bar")]);
assert_eq!(list.contains_ci(&v_txt("foo")), Some(true));
assert_eq!(list.contains_ci(&v_txt("BAR")), Some(true));
assert_eq!(list.contains_ci(&v_txt("baz")), Some(false));
}
#[test]
fn list_contains_any_ci() {
let haystack = Value::from_slice(&[v_txt("Apple"), v_txt("Banana")]);
let needles_yes = Value::from_slice(&[v_txt("banana"), v_txt("Cherry")]);
let needles_no = Value::from_slice(&[v_txt("pear"), v_txt("cherry")]);
assert_eq!(haystack.contains_any_ci(&needles_yes), Some(true));
assert_eq!(haystack.contains_any_ci(&needles_no), Some(false));
}
#[test]
fn list_contains_all_ci() {
let haystack = Value::from_slice(&[v_txt("Dog"), v_txt("Cat"), v_txt("Bird")]);
let needles_yes = Value::from_slice(&[v_txt("dog"), v_txt("cat")]);
let needles_no = Value::from_slice(&[v_txt("dog"), v_txt("lion")]);
assert_eq!(haystack.contains_all_ci(&needles_yes), Some(true));
assert_eq!(haystack.contains_all_ci(&needles_no), Some(false));
}
#[test]
fn scalar_vs_list_ci() {
let scalar = v_txt("Hello");
let list = Value::from_slice(&[v_txt("HELLO"), v_txt("World")]);
assert_eq!(scalar.in_list_ci(&list), Some(true));
assert_eq!(scalar.contains_any_ci(&list), Some(true));
let list2 = Value::from_slice(&[v_txt("World")]);
assert_eq!(scalar.contains_any_ci(&list2), Some(false));
}
#[test]
fn ci_membership_with_empty_lists() {
let empty = Value::from_slice::<Value>(&[]);
let scalar = v_txt("alpha");
assert_eq!(scalar.in_list_ci(&empty), Some(false));
assert_eq!(scalar.contains_any_ci(&empty), Some(false));
assert_eq!(scalar.contains_all_ci(&empty), Some(false));
}
#[test]
fn ci_equality_parses_identifier_text() {
let ulid = Ulid::generate();
let ulid_text = Value::Text(ulid.to_string());
assert!(Value::Ulid(ulid).contains_ci(&ulid_text).unwrap());
assert!(
Value::Ulid(ulid)
.in_list_ci(&Value::from_slice(&[ulid_text]))
.unwrap()
);
}
#[test]
fn ci_membership_handles_ulid_strings() {
let target = Ulid::generate();
let actual = Value::from_slice(&[Value::Ulid(target)]);
let needles = Value::from_slice(&[Value::Text(target.to_string())]);
assert_eq!(actual.contains_any_ci(&needles), Some(true));
assert_eq!(actual.contains_all_ci(&needles), Some(true));
}
#[test]
fn text_eq_cs_vs_ci() {
let a = v_txt("Alpha");
let b = v_txt("alpha");
assert_eq!(a.text_eq(&b, TextMode::Cs), Some(false));
assert_eq!(a.text_eq(&b, TextMode::Ci), Some(true));
}
#[test]
fn text_contains_starts_ends_cs_ci() {
let a = v_txt("Hello Alpha World");
assert_eq!(a.text_contains(&v_txt("alpha"), TextMode::Cs), Some(false));
assert_eq!(a.text_contains(&v_txt("alpha"), TextMode::Ci), Some(true));
assert_eq!(
a.text_starts_with(&v_txt("hello"), TextMode::Cs),
Some(false)
);
assert_eq!(
a.text_starts_with(&v_txt("hello"), TextMode::Ci),
Some(true)
);
assert_eq!(a.text_ends_with(&v_txt("WORLD"), TextMode::Cs), Some(false));
assert_eq!(a.text_ends_with(&v_txt("WORLD"), TextMode::Ci), Some(true));
}
#[test]
fn eq_and_ne_none_semantics() {
let some_val = v_i(42);
let none_val = Value::Null;
assert!(none_val == Value::Null);
assert!(some_val != Value::Null);
assert!(none_val == Value::Null);
assert!(some_val != Value::Null);
}