use bevy::ecs::system::RunSystemOnce;
use bevy::prelude::*;
use bevy::ui::FocusPolicy;
use super::{ElementEvents, EventSubscriptions};
use crate::bridge::{OutboundResource, StyleVariants};
use crate::protocol::op::Op;
use crate::protocol::outbound::Outbound;
use crate::reconcile::test_util::{create, ent, op_app, op_app_with, update};
use crate::style::props::FOCUS_POLICY;
fn observe_outbound(app: &mut App) -> tokio::sync::mpsc::UnboundedReceiver<Outbound> {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
app.insert_resource(OutboundResource(tx));
rx
}
fn drain(rx: &mut tokio::sync::mpsc::UnboundedReceiver<Outbound>) -> Vec<Outbound> {
std::iter::from_fn(|| rx.try_recv().ok()).collect()
}
#[test]
fn button_default_style_falls_back_on_unset() {
let (mut app, tx) = op_app();
tx.send(vec![create(
1,
"button",
serde_json::json!({ "hoverStyle": { "backgroundColor": "red" } }),
)])
.unwrap();
app.update();
let e = ent(&app, 1);
assert_eq!(app.world().get::<FocusPolicy>(e), Some(&FocusPolicy::Block));
assert_eq!(
app.world()
.get::<StyleVariants>(e)
.and_then(|v| v.base.as_ref())
.and_then(|s| s.get(&FOCUS_POLICY)),
Some(&FocusPolicy::Block),
"the variants' base is the effective style"
);
tx.send(vec![update(
1,
"button",
serde_json::json!({ "style": { "focusPolicy": "pass" } }),
&[],
&[],
)])
.unwrap();
app.update();
assert_eq!(app.world().get::<FocusPolicy>(e), Some(&FocusPolicy::Pass));
tx.send(vec![update(
1,
"button",
serde_json::json!({}),
&[],
&["focusPolicy"],
)])
.unwrap();
app.update();
assert_eq!(
app.world().get::<FocusPolicy>(e),
Some(&FocusPolicy::Block),
"unsetting the user value shows the default through"
);
}
#[cfg(all(feature = "devtools", debug_assertions))]
#[test]
fn background_image_on_image_warns_style_ignored() {
use crate::background_image::RBackgroundTexture;
let _lock = crate::diag::test_lock();
crate::diag::arm_runtime();
let _ = crate::diag::take_runtime_warnings();
let (mut app, tx) = op_app();
tx.send(vec![create(
77,
"image",
serde_json::json!({
"src": "a.png",
"style": { "backgroundImage": { "src": { "texture": "x" } } },
}),
)])
.unwrap();
app.update();
let e = ent(&app, 77);
assert!(app.world().get::<RBackgroundTexture>(e).is_none());
let mine: Vec<_> = crate::diag::take_runtime_warnings()
.into_iter()
.filter(|w| w.node == Some(77))
.collect();
assert!(
mine.iter()
.any(|w| w.kind == "styleIgnored" && w.value == "backgroundImage"),
"got {mine:?}"
);
}
static PING: super::ElementEvent<f32> = super::ElementEvent::new("ping").unconditional();
static PROBE: super::Element = super::Element {
events: &[&PING],
..super::Element::new("probe")
};
#[test]
fn element_events_follow_subscriptions() {
use crate::ReactAppExt;
use crate::elements::editable::CHANGE;
let (mut app, tx) = op_app_with(|app| {
app.add_react_element(&PROBE);
});
let mut rx = observe_outbound(&mut app);
tx.send(vec![
create(1, "editableText", serde_json::json!({ "value": "a" })),
create(2, "probe", serde_json::json!({})),
])
.unwrap();
app.update();
let (input, probe) = (ent(&app, 1), ent(&app, 2));
assert!(app.world().get::<EventSubscriptions>(input).is_none());
let send_change = move |events: ElementEvents| events.send(input, &CHANGE, &"b".to_owned());
assert!(
!app.world_mut().run_system_once(send_change).unwrap(),
"no onChange: nothing sent"
);
let ping = move |events: ElementEvents| events.send(probe, &PING, &4.0);
assert!(
app.world_mut().run_system_once(ping).unwrap(),
"an unconditional event needs no handler"
);
tx.send(vec![update(
1,
"editableText",
serde_json::json!({ "onChange": true }),
&[],
&[],
)])
.unwrap();
app.update();
assert!(app.world().get::<EventSubscriptions>(input).is_some());
assert!(app.world_mut().run_system_once(send_change).unwrap());
let sent = drain(&mut rx);
let brief: Vec<_> = sent
.iter()
.filter_map(|o| match o {
Outbound::ElementEvent { id, event, payload } => {
Some((*id, event.clone(), payload.clone()))
}
_ => None,
})
.collect();
assert_eq!(
brief,
vec![
(2, "ping".to_owned(), serde_json::json!(4.0)),
(1, "change".to_owned(), serde_json::json!("b")),
]
);
tx.send(vec![Op::Update {
id: 1,
props: Box::default(),
unset: vec!["onChange".into()],
style_unset: vec![],
}])
.unwrap();
app.update();
assert!(app.world().get::<EventSubscriptions>(input).is_none());
assert!(!app.world_mut().run_system_once(send_change).unwrap());
}