use gpui::{
App, Context, Element as _, IntoElement, ParentElement as _, Render, RenderOnce as _, Role,
Styled as _, TestAppContext, VisualTestContext, Window, accesskit, canvas, div, px,
};
use gpui_ai::prelude::{ChoiceEvent, ChoiceGroup, ChoiceOption};
use std::sync::{Arc, Mutex};
struct Captured {
role: Option<Role>,
node: accesskit::Node,
}
fn read<E: IntoElement>(element: E) -> Captured {
let element = element.into_element();
let role = element.a11y_role();
let mut node = accesskit::Node::new(Role::Unknown);
element.write_a11y_info(&mut node);
Captured { role, node }
}
type Capture = std::rc::Rc<dyn Fn(&mut Window, &mut App) -> Captured>;
struct Probe {
capture: Capture,
captured: Arc<Mutex<Option<Captured>>>,
}
impl Render for Probe {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let captured = self.captured.clone();
let capture = self.capture.clone();
canvas(
move |_, window, cx| {
*captured.lock().expect("capture mutex") = Some(capture(window, cx));
},
|_, _, _, _| {},
)
}
}
fn capture(cx: &mut TestAppContext, capture: Capture) -> Captured {
cx.update(gpui_ai::init);
let captured = Arc::new(Mutex::new(None));
let result = captured.clone();
let (_, cx) = cx.add_window_view(move |_, _| Probe { capture, captured });
cx.update(|window, cx| window.draw(cx).clear(cx));
result
.lock()
.expect("capture mutex")
.take()
.expect("the control writes an accessibility node")
}
#[gpui::test]
fn a_choice_group_announces_itself_and_its_size(cx: &mut TestAppContext) {
let captured = capture(
cx,
std::rc::Rc::new(|window, cx| {
read(
ChoiceGroup::new("flavours", "How many flavours?")
.options([
ChoiceOption::new("three", "Three"),
ChoiceOption::new("five", "Five"),
ChoiceOption::new("one", "Just one"),
])
.selected("five")
.render(window, cx),
)
}),
);
assert_eq!(captured.role, Some(Role::RadioGroup));
assert_eq!(captured.node.label(), Some("How many flavours?"));
assert_eq!(captured.node.size_of_set(), Some(3));
}
struct ChoiceProbe {
chosen: Arc<Mutex<Vec<String>>>,
}
impl Render for ChoiceProbe {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let chosen = self.chosen.clone();
div().w(px(320.)).child(
ChoiceGroup::new("flavours", "How many flavours?")
.options([
ChoiceOption::new("three", "Three").description("The core line"),
ChoiceOption::new("five", "Five"),
])
.selected("three")
.on_event(move |event, _, _| {
let ChoiceEvent::Chosen { option, .. } = event;
chosen
.lock()
.expect("chosen mutex")
.push(option.to_string());
}),
)
}
}
#[gpui::test]
fn every_option_keeps_one_text_inset(cx: &mut TestAppContext) {
cx.update(gpui_ai::init);
let (_, cx) = cx.add_window_view(|_, _| ChoiceProbe {
chosen: Arc::new(Mutex::new(Vec::new())),
});
let cx: &mut VisualTestContext = cx;
cx.update(|window, cx| window.draw(cx).clear(cx));
let with_description = cx
.debug_bounds("choice-flavours-three")
.expect("the described option renders");
let without = cx
.debug_bounds("choice-flavours-five")
.expect("the plain option renders");
assert_eq!(
with_description.origin.x, without.origin.x,
"options must share a left edge"
);
assert!(
with_description.size.height > without.size.height,
"the described option is the taller one, so this compares the two cases"
);
}
#[gpui::test]
fn an_indicator_sits_on_the_middle_of_the_first_line(cx: &mut TestAppContext) {
cx.update(gpui_ai::init);
let (_, cx) = cx.add_window_view(|_, _| ChoiceProbe {
chosen: Arc::new(Mutex::new(Vec::new())),
});
let cx: &mut VisualTestContext = cx;
cx.update(|window, cx| window.draw(cx).clear(cx));
for (option, seat_name, label_name) in [
(
"three",
"choice-seat-flavours-three",
"choice-label-flavours-three",
),
(
"five",
"choice-seat-flavours-five",
"choice-label-flavours-five",
),
] {
let seat = cx
.debug_bounds(seat_name)
.unwrap_or_else(|| panic!("{option} renders a seat"));
let label = cx
.debug_bounds(label_name)
.unwrap_or_else(|| panic!("{option} renders a label"));
assert!(
(seat.center().y - label.center().y).abs() <= px(0.5),
"{option}'s indicator must sit on its first line: seat centred at {:?}, the line \
at {:?}",
seat.center().y,
label.center().y
);
assert_eq!(
seat.size.height, label.size.height,
"{option}'s seat must be the line it sits on, not the control it holds"
);
}
}