use std::cell::{Cell, RefCell};
use std::rc::Rc;
use gpui::{IntoElement, ParentElement, Styled, TestAppContext, div, px};
use gpui_kit::prelude::*;
use gpui_kit_testkit::harness::Harness;
#[gpui::test]
fn a_known_extent_is_published_as_a_position(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
ProgressBar::new("index.progress")
.label("Indexing")
.count(3, 12)
.into_any_element()
});
let node = harness.node("index.progress").expect("published");
assert_eq!(node.value_min, Some(0.0));
assert_eq!(node.value_max, Some(1.0));
assert_eq!(node.value_now, Some(0.25));
assert_eq!(node.value.as_deref(), Some("3 of 12"));
assert!(node.busy);
}
#[gpui::test]
fn an_unknown_extent_reports_no_position(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
ProgressBar::new("index.progress")
.label("Contacting host")
.into_any_element()
});
let node = harness.node("index.progress").expect("published");
assert_eq!(node.value_now, None, "an unknown extent must not claim one");
assert!(node.busy);
}
#[gpui::test]
fn a_total_of_zero_stays_indeterminate(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
ProgressBar::new("index.progress")
.label("Indexing")
.count(0, 0)
.into_any_element()
});
assert_eq!(
harness.node("index.progress").expect("published").value_now,
None
);
}
#[gpui::test]
fn a_tag_reports_its_own_removal(cx: &mut TestAppContext) {
let removals = Rc::new(RefCell::new(0usize));
let sink = removals.clone();
let mut harness = Harness::new(cx, gpui_kit::install, move |_, _| {
let sink = sink.clone();
Tag::new("filter.rust", "rust")
.on_remove(move |_, _| *sink.borrow_mut() += 1)
.into_any_element()
});
harness.click("filter.rust.remove");
assert_eq!(*removals.borrow(), 1);
assert_eq!(
harness
.node("filter.rust.remove")
.expect("published")
.text
.as_deref(),
Some("Remove rust")
);
}
#[gpui::test]
fn a_tag_that_cannot_be_removed_offers_no_way_to(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
Tag::new("filter.pinned", "pinned")
.disabled(true)
.on_remove(|_, _| {})
.into_any_element()
});
assert!(
harness.node("filter.pinned.remove").is_none(),
"a refused removal must not publish a control"
);
}
#[gpui::test]
fn an_empty_surface_says_which_fact_it_is_reporting(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
div()
.flex()
.flex_col()
.child(
EmptyState::new("runs.empty", "No runs yet")
.kind(EmptyKind::Unstarted)
.detail("A run appears here once one has been started."),
)
.child(
EmptyState::new("runs.refused", "The host refused the request")
.kind(EmptyKind::Unavailable),
)
.into_any_element()
});
assert_eq!(
harness
.node("runs.empty")
.expect("published")
.value
.as_deref(),
Some("unstarted")
);
assert_eq!(
harness
.node("runs.refused")
.expect("published")
.value
.as_deref(),
Some("unavailable"),
"a refusal must not be reported as an absence of data"
);
}
#[gpui::test]
fn an_avatar_and_a_divider_appear_only_when_named(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
div()
.flex()
.flex_col()
.child(Avatar::new("Ada Lovelace").id("member.ada"))
.child(Avatar::new("Unnamed"))
.child(Divider::new().id("section.rule").label("Filters"))
.child(Divider::new())
.into_any_element()
});
let snapshot = harness.snapshot();
assert_eq!(
snapshot
.find("member.ada")
.and_then(|node| node.text.as_deref()),
Some("Ada Lovelace")
);
assert_eq!(
snapshot
.find("section.rule")
.and_then(|node| node.text.as_deref()),
Some("Filters")
);
assert_eq!(
snapshot.nodes.len(),
2,
"decorative marks must not add noise to the tree"
);
}
#[gpui::test]
fn browser_states_publish_one_truthful_current_contract(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
div()
.flex()
.flex_col()
.child(
div()
.w(px(260.0))
.h(px(150.0))
.child(BrowserPanel::new("browser.loading").state(ViewportState::Loading)),
)
.child(
div()
.w(px(260.0))
.h(px(150.0))
.child(BrowserPanel::new("browser.empty").state(ViewportState::Empty)),
)
.child(div().w(px(260.0)).h(px(150.0)).child(
BrowserPanel::new("browser.unavailable").state(ViewportState::Unavailable(
"The host refused this address.".into(),
)),
))
.child(
div().w(px(260.0)).h(px(150.0)).child(
BrowserPanel::new("browser.error")
.state(ViewportState::Error("Navigation failed.".into())),
),
)
.child(
div().w(px(260.0)).h(px(150.0)).child(
BrowserPanel::new("browser.ready")
.state(ViewportState::Ready)
.viewport(div().size_full().child("page")),
),
)
.into_any_element()
});
for (id, expected) in [
("browser.loading", "loading"),
("browser.empty", "empty"),
("browser.unavailable", "unavailable"),
("browser.error", "error"),
("browser.ready", "ready"),
] {
assert_eq!(
harness.node(id).expect("browser state").value.as_deref(),
Some(expected)
);
assert_eq!(
harness
.node(&format!("{id}.viewport"))
.expect("stable viewport")
.value
.as_deref(),
Some(expected)
);
}
assert!(harness.node("browser.loading").expect("loading").busy);
assert!(
!harness
.node("browser.unavailable")
.expect("unavailable")
.busy
);
}
#[gpui::test]
fn ready_without_a_host_viewport_reports_an_error(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
div()
.w(px(280.0))
.h(px(180.0))
.child(BrowserPanel::new("browser").state(ViewportState::Ready))
.into_any_element()
});
assert_eq!(
harness.node("browser").expect("browser").value.as_deref(),
Some("error")
);
assert_eq!(
harness
.node("browser.viewport.status")
.expect("host contract error")
.value
.as_deref(),
Some("failed")
);
}
#[gpui::test]
fn browser_actions_follow_tab_order_and_keyboard_activation(cx: &mut TestAppContext) {
let back = Rc::new(Cell::new(0));
let forward = Rc::new(Cell::new(0));
let reload = Rc::new(Cell::new(0));
let back_sink = Rc::clone(&back);
let forward_sink = Rc::clone(&forward);
let reload_sink = Rc::clone(&reload);
let mut harness = Harness::new(cx, gpui_kit::install, move |_, _| {
let back = Rc::clone(&back_sink);
let forward = Rc::clone(&forward_sink);
let reload = Rc::clone(&reload_sink);
div()
.w(px(320.0))
.h(px(180.0))
.child(
BrowserPanel::new("browser")
.on_back(move |_, _| back.set(back.get() + 1))
.on_forward(move |_, _| forward.set(forward.get() + 1))
.on_reload(move |_, _| reload.set(reload.get() + 1)),
)
.into_any_element()
});
for (id, name, key) in [
("browser.back", "Back", "enter"),
("browser.forward", "Forward", "space"),
("browser.reload", "Reload", "enter"),
] {
assert!(harness.node(id).is_some(), "stable semantic id {id}");
harness.update(|window, cx| window.focus_next(cx));
let tree = harness.accessibility_tree();
let focused = tree["gpui_focus"].as_str().expect("focused node");
assert_eq!(tree["nodes"][focused]["aria"]["label"], name);
harness.keystrokes(key);
}
assert_eq!((back.get(), forward.get(), reload.get()), (1, 1, 1));
}
#[gpui::test]
fn browser_long_content_stays_inside_a_narrow_contract(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
div()
.w(px(184.0))
.h(px(160.0))
.child(
BrowserPanel::new("browser")
.url("https://example.com/a/very/long/path/that/must/not/widen/the/panel")
.state(ViewportState::Ready)
.viewport(div().size_full().child(
"A long host-owned page line remains clipped to this narrow viewport.",
)),
)
.into_any_element()
});
let panel = harness.bounds("browser").expect("panel bounds");
let address = harness.bounds("browser.address").expect("address bounds");
let viewport = harness.bounds("browser.viewport").expect("viewport bounds");
assert!(f32::from(panel.size.width) <= 184.0);
assert!(address.right() <= panel.right());
assert!(viewport.right() <= panel.right());
}