use super::*;
use crate::{
Root,
group_box::GroupBoxVariant,
setting::{SettingGroup, SettingItem},
};
use gpui::{
Context, InteractiveElement as _, Render, TestAppContext, VisualTestContext, point, size,
};
struct SettingsHost {
pages: Vec<SettingPage>,
state: Option<Entity<SettingsState>>,
}
impl Render for SettingsHost {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let settings = Settings::new("search-test")
.pages(self.pages.clone())
.default_selected_index(SelectIndex {
page_ix: 1,
group_ix: None,
})
.render(window, cx)
.into_any_element();
self.state = Some(window.use_keyed_state("search-test", cx, |_, _| unreachable!()));
div().size_full().child(settings)
}
}
fn item(keyword: &'static str) -> SettingItem {
item_with_height(keyword, 80.)
}
fn item_with_height(keyword: &'static str, height: f32) -> SettingItem {
SettingItem::render(move |options, _, _| {
let selector = format!(
"setting-{}-{}-{}",
options.page_ix(),
options.group_ix(),
options.item_ix()
);
div()
.h(px(height))
.child("Setting")
.debug_selector(move || selector.clone())
})
.keywords([keyword])
}
fn setup(cx: &mut TestAppContext) -> (Entity<SettingsHost>, &mut VisualTestContext) {
cx.update(|cx| {
crate::init(cx);
crate::Theme::global_mut(cx).font_size = px(16.);
});
let pages = vec![
SettingPage::new("General").group(SettingGroup::new().item(item("language"))),
SettingPage::new("Appearance").default_open(true).groups([
SettingGroup::new().item(item("unrelated")),
SettingGroup::new()
.title("Colors")
.item(item("theme colors")),
SettingGroup::new()
.title("Fonts")
.items([item("unrelated"), item("theme font")]),
]),
SettingPage::new("Editor").group(SettingGroup::new().item(item("theme editor"))),
];
let mut host = None;
let (_, cx) = cx.add_window_view(|window, cx| {
let view = cx.new(|_| SettingsHost { pages, state: None });
host = Some(view.clone());
Root::new(view, window, cx)
});
cx.simulate_resize(size(px(1000.), px(700.)));
draw(cx);
(host.unwrap(), cx)
}
fn draw(cx: &mut VisualTestContext) {
cx.run_until_parked();
cx.update(|window, cx| window.draw(cx).clear(cx));
}
fn search(host: &Entity<SettingsHost>, query: &str, cx: &mut VisualTestContext) {
cx.update(|window, cx| {
let input = host
.read(cx)
.state
.as_ref()
.unwrap()
.read(cx)
.search_input
.clone();
input.update(cx, |input, cx| input.set_value(query, window, cx));
});
draw(cx);
}
fn selection(host: &Entity<SettingsHost>, cx: &mut VisualTestContext) -> (usize, Option<usize>) {
cx.update(|_, cx| {
let selected = host
.read(cx)
.state
.as_ref()
.unwrap()
.read(cx)
.selected_index;
(selected.page_ix, selected.group_ix)
})
}
fn click_nav(y: f32, cx: &mut VisualTestContext) {
cx.simulate_click(point(px(50.), px(y)), Default::default());
draw(cx);
}
#[gpui::test]
fn search_preserves_the_page_and_clicks_use_original_indices(cx: &mut TestAppContext) {
let (host, cx) = setup(cx);
search(&host, "theme", cx);
assert_eq!(selection(&host, cx), (1, None));
assert!(cx.debug_bounds("setting-1-1-0").is_some());
assert!(cx.debug_bounds("setting-2-0-0").is_none());
search(&host, "font", cx);
assert_eq!(selection(&host, cx), (1, None));
assert!(cx.debug_bounds("setting-1-2-1").is_some());
search(&host, "theme", cx);
click_nav(156., cx);
assert_eq!(selection(&host, cx), (2, None));
search(&host, "", cx);
assert_eq!(selection(&host, cx), (2, None));
assert!(cx.debug_bounds("setting-2-0-0").is_some());
search(&host, "font", cx);
assert_eq!(selection(&host, cx), (1, None));
assert!(cx.debug_bounds("setting-1-2-1").is_some());
search(&host, "no matching setting", cx);
assert_eq!(selection(&host, cx), (1, None));
assert!(cx.debug_bounds("setting-1-2-1").is_none());
search(&host, "", cx);
assert_eq!(selection(&host, cx), (1, None));
}
#[gpui::test]
fn search_preserves_group_and_item_identity(cx: &mut TestAppContext) {
let (host, cx) = setup(cx);
cx.update(|_, cx| {
host.update(cx, |host, cx| {
host.pages[1].groups[1].items = vec![item_with_height("theme colors", 450.)];
cx.notify();
});
});
draw(cx);
click_nav(156., cx);
assert_eq!(selection(&host, cx), (1, Some(2)));
search(&host, "theme", cx);
assert_eq!(selection(&host, cx), (1, Some(2)));
assert!(cx.debug_bounds("setting-1-2-1").is_some());
assert!(cx.debug_bounds("setting-1-2-0").is_none());
click_nav(120., cx);
assert_eq!(selection(&host, cx), (1, Some(2)));
let target = cx.debug_bounds("setting-1-2-1").unwrap();
assert!(target.top() >= px(0.) && target.bottom() <= px(700.));
search(&host, "font", cx);
assert_eq!(selection(&host, cx), (1, Some(2)));
search(&host, "", cx);
assert_eq!(selection(&host, cx), (1, Some(2)));
search(&host, "colors", cx);
assert_eq!(selection(&host, cx), (1, None));
assert!(cx.debug_bounds("setting-1-1-0").is_some());
}
#[gpui::test]
fn footer_follows_group_search_visibility(cx: &mut TestAppContext) {
let (host, cx) = setup(cx);
cx.update(|_, cx| {
host.update(cx, |host, cx| {
host.pages[1].groups[2] = host.pages[1].groups[2].clone().footer(|_, _| {
div()
.child("Changes apply to this device only.")
.debug_selector(|| "font-footer".into())
});
cx.notify();
});
});
search(&host, "font", cx);
assert!(cx.debug_bounds("setting-1-2-1").is_some());
assert!(cx.debug_bounds("font-footer").is_some());
search(&host, "colors", cx);
assert!(cx.debug_bounds("setting-1-1-0").is_some());
assert!(cx.debug_bounds("font-footer").is_none());
search(&host, "font", cx);
assert!(cx.debug_bounds("font-footer").is_some());
assert_eq!(selection(&host, cx), (1, None));
}
#[gpui::test]
fn resetting_search_results_leaves_hidden_settings_unchanged(cx: &mut TestAppContext) {
use std::{cell::Cell, rc::Rc};
let (_, cx) = setup(cx);
let visible = Rc::new(Cell::new(false));
let hidden = Rc::new(Cell::new(true));
let resettable_item = |keyword, dirty: &Rc<Cell<bool>>| {
let read = dirty.clone();
let reset = dirty.clone();
item(keyword).on_reset(move |_| read.get(), move |_, _| reset.set(false))
};
let group = SettingGroup::new().items([
resettable_item("theme", &visible),
resettable_item("hidden", &hidden),
]);
cx.update(|window, cx| {
assert!(!group.is_resettable("theme", cx));
visible.set(true);
assert!(group.is_resettable("theme", cx));
group.reset("theme", window, cx);
});
assert!(!visible.get());
assert!(hidden.get());
}
struct VariantHost {
pages: Vec<SettingPage>,
outline: bool,
}
impl Render for VariantHost {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let mut settings = Settings::new("variant-test").pages(self.pages.clone());
if self.outline {
settings = settings.with_group_variant(GroupBoxVariant::Outline);
}
div()
.size_full()
.child(settings.render(window, cx).into_any_element())
}
}
fn variant_pages(override_first_group: bool) -> Vec<SettingPage> {
let first = if override_first_group {
SettingGroup::new().variant(GroupBoxVariant::Normal)
} else {
SettingGroup::new()
};
vec![SettingPage::new("General").groups([
first.item(item("plain")),
SettingGroup::new().item(item("outlined")),
])]
}
#[gpui::test]
fn group_variant_overrides_the_settings_default(cx: &mut TestAppContext) {
cx.update(|cx| {
crate::init(cx);
crate::Theme::global_mut(cx).font_size = px(16.);
});
let open_window = |pages, outline, cx: &mut TestAppContext| {
let (_, cx) = cx.add_window_view(|window, cx| {
let view = cx.new(|_| VariantHost { pages, outline });
Root::new(view, window, cx)
});
cx.simulate_resize(size(px(1000.), px(700.)));
draw(cx);
(
cx.debug_bounds("setting-0-0-0").unwrap(),
cx.debug_bounds("setting-0-1-0").unwrap(),
)
};
let (baseline_first, baseline_second) = open_window(variant_pages(false), false, cx);
let (overridden_first, inherited_second) = open_window(variant_pages(true), true, cx);
assert_eq!(
f64::from(overridden_first.top() - baseline_first.top()),
0.,
"the overridden group must lose the global Outline chrome"
);
assert_eq!(
f64::from(inherited_second.top() - baseline_second.top()),
17.,
"groups without an override must keep the settings-level variant"
);
}
#[gpui::test]
fn selecting_a_group_from_another_page_scrolls_to_it(cx: &mut TestAppContext) {
let (host, cx) = setup(cx);
cx.update(|_, cx| {
host.update(cx, |host, cx| {
host.pages[1].groups[1].items = vec![item_with_height("theme colors", 900.)];
cx.notify();
});
});
draw(cx);
let select = |page_ix, group_ix: Option<usize>, cx: &mut VisualTestContext| {
cx.update(|_, cx| {
let state = host.read(cx).state.clone().unwrap();
state.update(cx, |state, cx| {
state.selected_index = SelectIndex { page_ix, group_ix };
state.deferred_scroll_group_ix = group_ix;
cx.notify();
});
});
draw(cx);
};
select(0, None, cx);
select(1, Some(2), cx);
let target = cx.debug_bounds("setting-1-2-1").unwrap();
assert!(target.top() >= px(0.) && target.bottom() <= px(700.));
}