use std::collections::BTreeMap;
use dioxus::prelude::*;
use crate::experiments::config::TrackedEvent;
pub type ExposureSink = EventHandler<TrackedEvent>;
#[component]
pub fn ExperimentTracker(experiment: String, variant: String, on_event: Option<ExposureSink>, children: Element) -> Element {
let exp_for_ctx = experiment.clone();
let var_for_ctx = variant.clone();
use_context_provider(move || ExperimentCtx {
experiment: exp_for_ctx,
variant: var_for_ctx,
on_event,
});
use_effect(move || {
if let Some(handler) = on_event {
handler.call(TrackedEvent::exposure(&experiment, &variant));
}
});
rsx! {
{children}
}
}
pub fn use_experiment() -> (String, String) {
let ctx = use_context::<ExperimentCtx>();
(ctx.experiment, ctx.variant)
}
pub fn use_experiment_event() -> impl Fn(&str, BTreeMap<String, String>) {
let ctx = use_context::<ExperimentCtx>();
move |action: &str, props: BTreeMap<String, String>| {
if let Some(handler) = ctx.on_event {
handler.call(TrackedEvent::action(&ctx.experiment, &ctx.variant, action, props));
}
}
}
#[derive(Clone)]
struct ExperimentCtx {
experiment: String,
variant: String,
on_event: Option<ExposureSink>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::experiments::test_util::render;
#[test]
fn tracker_provides_context_to_descendants() {
#[component]
fn Probe() -> Element {
let _track = use_experiment_event();
let (experiment, variant) = use_experiment();
rsx! {
span { "{experiment}:{variant}" }
}
}
fn app() -> Element {
rsx! {
ExperimentTracker { experiment: "hero".to_string(), variant: "b".to_string(), Probe {} }
}
}
let html = render(app);
assert!(html.contains("hero:b"), "{html}");
}
#[test]
fn tracker_renders_children() {
fn app() -> Element {
rsx! {
ExperimentTracker { experiment: "team".to_string(), variant: "a".to_string(),
p { "body" }
}
}
}
assert!(render(app).contains("body"));
}
#[test]
fn tracker_without_on_event_renders_without_panicking() {
#[component]
fn Probe() -> Element {
let _track = use_experiment_event();
let (experiment, variant) = use_experiment();
rsx! {
span { "{experiment}:{variant}" }
}
}
fn app() -> Element {
rsx! {
ExperimentTracker { experiment: "hero".to_string(), variant: "a".to_string(), on_event: None,
Probe {}
p { "body" }
}
}
}
let html = render(app);
assert!(html.contains("hero:a"), "{html}");
assert!(html.contains("body"), "{html}");
}
}