use fluxion_stream::emit_when::EmitWhenExt;
use fluxion_stream::CombinedState;
use fluxion_test_utils::{
helpers::{assert_no_element_emitted, assert_stream_ended, unwrap_stream},
test_channel,
test_data::{
animal_ant, animal_bird, animal_cat, animal_dog, animal_spider, person_alice, person_bob,
person_charlie, person_dave, person_diane, plant_rose, plant_sunflower, TestData,
},
unwrap_value, Sequenced,
};
#[tokio::test]
async fn test_emit_when_empty_streams() -> anyhow::Result<()> {
let filter_fn = |_: &CombinedState<TestData>| -> bool { true };
let (source_tx, source_stream) = test_channel::<Sequenced<TestData>>();
let (filter_tx, filter_stream) = test_channel::<Sequenced<TestData>>();
drop(source_tx);
drop(filter_tx);
let mut result = source_stream.emit_when(filter_stream, filter_fn);
assert_stream_ended(&mut result, 500).await;
Ok(())
}
#[tokio::test]
async fn test_emit_when_filter_compares_source_and_filter() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
let source_age = match &values[0] {
TestData::Person(p) => p.age,
_ => return false,
};
let filter_legs = match &values[1] {
TestData::Animal(a) => a.legs,
_ => return false,
};
source_age > filter_legs
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(person_alice()))?;
filter_tx.unbounded_send(Sequenced::new(animal_dog()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_alice(),
"Expected Alice to be emitted when age > legs"
);
filter_tx.unbounded_send(Sequenced::new(animal_spider()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_alice(),
"Expected Alice to be emitted when age > legs (spider)"
);
filter_tx.unbounded_send(Sequenced::new(animal_ant()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_alice(),
"Expected Alice to be emitted when age > legs (ant)"
);
Ok(())
}
#[tokio::test]
async fn test_emit_when_threshold_comparison() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
let source_height = match &values[0] {
TestData::Plant(p) => p.height,
_ => return false,
};
let filter_height = match &values[1] {
TestData::Plant(p) => p.height,
_ => return false,
};
source_height.abs_diff(filter_height) > 50
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(plant_rose()))?;
filter_tx.unbounded_send(Sequenced::new(plant_sunflower()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&plant_rose(),
"Expected Rose to be emitted when height difference > 50"
);
source_tx.unbounded_send(Sequenced::new(plant_sunflower()))?;
assert_no_element_emitted(&mut result, 100).await;
Ok(())
}
#[tokio::test]
async fn test_emit_when_name_length_comparison() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
let source_name = match &values[0] {
TestData::Person(p) => &p.name,
_ => return false,
};
let filter_name = match &values[1] {
TestData::Animal(a) => &a.species,
_ => return false,
};
source_name.len() > filter_name.len()
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(person_charlie()))?;
filter_tx.unbounded_send(Sequenced::new(animal_dog()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_charlie(),
"Expected Charlie to be emitted when name longer than Dog"
);
source_tx.unbounded_send(Sequenced::new(person_bob()))?;
assert_no_element_emitted(&mut result, 100).await;
filter_tx.unbounded_send(Sequenced::new(animal_cat()))?;
assert_no_element_emitted(&mut result, 100).await;
Ok(())
}
#[tokio::test]
async fn test_emit_when_multiple_source_updates_with_comparison() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
let source_age = match &values[0] {
TestData::Person(p) => p.age,
_ => return false,
};
let filter_legs = match &values[1] {
TestData::Animal(a) => a.legs,
_ => return false,
};
source_age % 2 == 0 && source_age > filter_legs
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
filter_tx.unbounded_send(Sequenced::new(animal_dog()))?;
source_tx.unbounded_send(Sequenced::new(person_alice()))?;
assert_no_element_emitted(&mut result, 100).await;
source_tx.unbounded_send(Sequenced::new(person_bob()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_bob(),
"Expected Bob (30, even) to be emitted"
);
source_tx.unbounded_send(Sequenced::new(person_dave()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_dave(),
"Expected Dave (28, even) to be emitted"
);
source_tx.unbounded_send(Sequenced::new(person_charlie()))?;
assert_no_element_emitted(&mut result, 100).await;
Ok(())
}
#[tokio::test]
async fn test_emit_when_stateful_comparison() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
let source_age = match &values[0] {
TestData::Person(p) => p.age,
_ => return false,
};
let threshold_age = match &values[1] {
TestData::Person(p) => p.age,
_ => return false,
};
source_age > threshold_age
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
filter_tx.unbounded_send(Sequenced::new(person_bob()))?;
source_tx.unbounded_send(Sequenced::new(person_alice()))?;
assert_no_element_emitted(&mut result, 100).await;
source_tx.unbounded_send(Sequenced::new(person_charlie()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_charlie(),
"Expected Charlie to be emitted when age > threshold"
);
source_tx.unbounded_send(Sequenced::new(person_diane()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_diane(),
"Expected Diane to be emitted when age > threshold"
);
filter_tx.unbounded_send(Sequenced::new(person_diane()))?;
assert_no_element_emitted(&mut result, 100).await;
source_tx.unbounded_send(Sequenced::new(person_bob()))?;
assert_no_element_emitted(&mut result, 100).await;
Ok(())
}
#[tokio::test]
async fn test_emit_when_filter_stream_closes() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
matches!(&values[0], TestData::Person(_)) && matches!(&values[1], TestData::Animal(_))
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(person_alice()))?;
filter_tx.unbounded_send(Sequenced::new(animal_dog()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(&emitted_item.value, &person_alice());
drop(filter_tx);
source_tx.unbounded_send(Sequenced::new(person_bob()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_bob(),
"Expected source updates to continue after filter stream closes"
);
Ok(())
}
#[tokio::test]
async fn test_emit_when_both_values_required() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
matches!(&values[0], TestData::Person(_)) && matches!(&values[1], TestData::Animal(_))
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(person_alice()))?;
assert_no_element_emitted(&mut result, 100).await;
filter_tx.unbounded_send(Sequenced::new(animal_dog()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_alice(),
"Expected Alice to be emitted after both values are present"
);
Ok(())
}
#[tokio::test]
async fn test_emit_when_filter_stream_updates_trigger_reevaluation() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
let source_age = match &values[0] {
TestData::Person(p) => p.age,
_ => return false,
};
let filter_legs = match &values[1] {
TestData::Animal(a) => a.legs,
_ => return false,
};
source_age >= filter_legs * 10
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(person_alice()))?;
filter_tx.unbounded_send(Sequenced::new(animal_bird()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(&emitted_item.value, &person_alice());
filter_tx.unbounded_send(Sequenced::new(animal_dog()))?;
assert_no_element_emitted(&mut result, 100).await;
filter_tx.unbounded_send(Sequenced::new(animal_bird()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(&emitted_item.value, &person_alice());
Ok(())
}
#[tokio::test]
async fn test_emit_when_delta_based_filtering() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
let source_age = match &values[0] {
TestData::Person(p) => p.age,
_ => return false,
};
let filter_age = match &values[1] {
TestData::Person(p) => p.age,
_ => return false,
};
source_age.abs_diff(filter_age) > 10
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(person_alice()))?;
filter_tx.unbounded_send(Sequenced::new(person_bob()))?;
assert_no_element_emitted(&mut result, 100).await;
source_tx.unbounded_send(Sequenced::new(person_diane()))?;
assert_no_element_emitted(&mut result, 100).await;
filter_tx.unbounded_send(Sequenced::new(person_alice()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_diane(),
"Expected Diane to be emitted when age difference > 10"
);
Ok(())
}
#[tokio::test]
async fn test_emit_when_cross_type_comparison() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
let source_legs = match &values[0] {
TestData::Animal(a) => a.legs,
_ => return false,
};
let filter_age = match &values[1] {
TestData::Person(p) => p.age,
_ => return false,
};
source_legs == filter_age
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(animal_dog()))?;
filter_tx.unbounded_send(Sequenced::new(person_alice()))?;
assert_no_element_emitted(&mut result, 100).await;
source_tx.unbounded_send(Sequenced::new(animal_spider()))?;
assert_no_element_emitted(&mut result, 100).await;
source_tx.unbounded_send(Sequenced::new(animal_ant()))?;
assert_no_element_emitted(&mut result, 100).await;
Ok(())
}
#[tokio::test]
async fn test_emit_when_source_stream_closes_after_filter() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |_: &CombinedState<TestData>| -> bool { true };
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(person_alice()))?;
filter_tx.unbounded_send(Sequenced::new(animal_dog()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(&emitted_item.value, &person_alice());
drop(source_tx);
filter_tx.unbounded_send(Sequenced::new(animal_cat()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(
&emitted_item.value,
&person_alice(),
"Expected filter updates to re-emit latest source after source closes"
);
filter_tx.unbounded_send(Sequenced::new(animal_spider()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(&emitted_item.value, &person_alice());
Ok(())
}
#[tokio::test]
#[should_panic(expected = "Filter function must not panic!")]
async fn test_emit_when_filter_panics() {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn =
|_: &CombinedState<TestData>| -> bool { panic!("Filter function must not panic!") };
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx
.unbounded_send(Sequenced::new(person_alice()))
.unwrap();
filter_tx
.unbounded_send(Sequenced::new(animal_dog()))
.unwrap();
let _ = unwrap_stream(&mut result, 100).await;
}
#[tokio::test]
async fn test_emit_when_complex_multi_condition() -> anyhow::Result<()> {
let (source_tx, source_stream) = test_channel();
let (filter_tx, filter_stream) = test_channel();
let filter_fn = |state: &CombinedState<TestData>| -> bool {
let values = state.values();
let source_age = match &values[0] {
TestData::Person(p) => p.age,
_ => return false,
};
let filter_legs = match &values[1] {
TestData::Animal(a) => a.legs,
_ => return false,
};
source_age % 2 == 0 && filter_legs > 2 && source_age % filter_legs == 0
};
let mut result = source_stream.emit_when(filter_stream, filter_fn);
source_tx.unbounded_send(Sequenced::new(person_diane()))?;
filter_tx.unbounded_send(Sequenced::new(animal_dog()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(&emitted_item.value, &person_diane());
source_tx.unbounded_send(Sequenced::new(person_bob()))?;
assert_no_element_emitted(&mut result, 100).await;
filter_tx.unbounded_send(Sequenced::new(animal_ant()))?;
let emitted_item = unwrap_value(Some(unwrap_stream(&mut result, 500).await));
assert_eq!(&emitted_item.value, &person_bob());
source_tx.unbounded_send(Sequenced::new(person_alice()))?;
assert_no_element_emitted(&mut result, 100).await;
Ok(())
}