use std::cell::RefCell;
use std::rc::Rc;
use gpui::{
AppContext as _, Entity, IntoElement, Modifiers, MouseButton, MouseDownEvent, SharedString,
TestAppContext, div, prelude::*,
};
use gpui_kit::prelude::*;
use gpui_kit_semantics::Role;
use gpui_kit_testkit::harness::Harness;
type Calls<T> = Rc<RefCell<Vec<T>>>;
fn recorder<T: 'static>() -> (Calls<T>, Calls<T>) {
let calls: Calls<T> = Rc::new(RefCell::new(Vec::new()));
(calls.clone(), calls)
}
fn middle_click(harness: &mut Harness, id: &str) {
let at = harness.point_in(id);
harness.context().simulate_event(MouseDownEvent {
button: MouseButton::Middle,
position: at,
modifiers: Modifiers::none(),
click_count: 1,
first_mouse: false,
});
harness.context().run_until_parked();
}
fn document_tabs(
cx: &mut TestAppContext,
closable: bool,
) -> (Harness, Calls<String>, Calls<String>) {
let (selected, select_sink) = recorder::<String>();
let (closed, close_sink) = recorder::<String>();
let harness = Harness::new(cx, gpui_kit::install, move |_, _| {
let select_sink = select_sink.clone();
let close_sink = close_sink.clone();
Tabs::new("editor.tabs")
.tabs([
TabItem::new("readme", "README.md").closable(closable),
TabItem::new("main", "main.rs").dirty().closable(closable),
TabItem::new("theme", "theme.json")
.saving()
.closable(closable),
TabItem::new("notes", "notes.md")
.save_failed("The workspace is read-only.")
.closable(closable),
])
.selected("main")
.on_select(move |id, _, _| select_sink.borrow_mut().push(id.to_string()))
.on_close(move |id, _, _| close_sink.borrow_mut().push(id.to_string()))
.into_any_element()
});
(harness, selected, closed)
}
#[gpui::test]
fn a_clean_tab_says_nothing_and_the_other_three_say_different_things(cx: &mut TestAppContext) {
let (mut harness, _selected, _closed) = document_tabs(cx, true);
assert!(harness.node("editor.tabs.readme.save").is_none());
let dirty = harness.node("editor.tabs.main.save").expect("published");
let saving = harness.node("editor.tabs.theme.save").expect("published");
let failed = harness.node("editor.tabs.notes.save").expect("published");
assert_eq!(dirty.value.as_deref(), Some("dirty"));
assert_eq!(saving.value.as_deref(), Some("saving"));
assert_eq!(failed.value.as_deref(), Some("save-failed"));
assert!(saving.busy && !saving.invalid);
assert!(failed.invalid && !failed.busy);
assert!(!dirty.busy && !dirty.invalid);
assert_eq!(failed.text.as_deref(), Some("The workspace is read-only."));
}
#[gpui::test]
fn closing_a_tab_does_not_also_switch_to_it(cx: &mut TestAppContext) {
let (mut harness, selected, closed) = document_tabs(cx, true);
harness.click("editor.tabs.readme.close");
assert_eq!(*closed.borrow(), vec!["readme".to_string()]);
assert!(selected.borrow().is_empty());
}
#[gpui::test]
fn a_middle_click_on_a_tab_puts_it_away(cx: &mut TestAppContext) {
let (mut harness, selected, closed) = document_tabs(cx, true);
middle_click(&mut harness, "editor.tabs.theme");
assert_eq!(*closed.borrow(), vec!["theme".to_string()]);
assert!(selected.borrow().is_empty());
}
#[gpui::test]
fn a_tab_nobody_can_close_offers_no_control(cx: &mut TestAppContext) {
let (mut harness, _selected, _closed) = document_tabs(cx, false);
assert!(harness.node("editor.tabs.readme.close").is_none());
assert!(harness.node("editor.tabs.main.close").is_none());
}
#[gpui::test]
fn overflowed_tabs_are_relocated_and_not_dropped(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |window, cx| {
let overflow = cx.new(|cx| Menu::new("editor.tabs.menu", window, cx).trigger("More"));
Tabs::new("editor.tabs")
.tabs([
TabItem::new("one", "adapter.rs"),
TabItem::new("two", "catalog.rs"),
TabItem::new("three", "harness.rs"),
TabItem::new("four", "registry.rs"),
TabItem::new("five", "transport.rs"),
])
.selected("two")
.overflow_after(3)
.overflow_menu(overflow)
.on_select(|_, _, _| {})
.into_any_element()
});
assert!(harness.node("editor.tabs.three").is_some());
assert!(harness.node("editor.tabs.four").is_none());
assert_eq!(
harness.node("editor.tabs").expect("published").value,
Some("5".into())
);
let overflow = harness.node("editor.tabs.overflow").expect("published");
assert_eq!(overflow.value.as_deref(), Some("2"));
}
#[gpui::test]
fn a_hidden_tab_can_still_be_reached_from_the_keyboard(cx: &mut TestAppContext) {
let (calls, sink) = recorder::<String>();
let mut harness = Harness::new(cx, gpui_kit::install, move |window, cx| {
let sink = sink.clone();
let overflow = cx.new(|cx| Menu::new("editor.tabs.menu", window, cx).trigger("More"));
Tabs::new("editor.tabs")
.tabs([
TabItem::new("one", "adapter.rs"),
TabItem::new("two", "catalog.rs"),
TabItem::new("three", "harness.rs"),
TabItem::new("four", "registry.rs"),
])
.selected("three")
.overflow_after(3)
.overflow_menu(overflow)
.on_select(move |id, _, _| sink.borrow_mut().push(id.to_string()))
.into_any_element()
});
harness.click("editor.tabs.three");
calls.borrow_mut().clear();
harness.keystrokes("right");
assert_eq!(*calls.borrow(), vec!["four".to_string()]);
}
fn search(cx: &mut TestAppContext, count: HitCount) -> (Harness, Entity<SearchField>) {
let held: Rc<RefCell<Option<Entity<SearchField>>>> = Rc::new(RefCell::new(None));
let sink = held.clone();
let harness = Harness::new(cx, gpui_kit::install, move |window, cx| {
let field = sink
.borrow_mut()
.get_or_insert_with(|| {
let count = count.clone();
cx.new(|cx| {
let mut field = SearchField::new("find", window, cx);
field.set_count(count, cx);
field
})
})
.clone();
field.into_any_element()
});
let field = held.borrow().clone().expect("built");
(harness, field)
}
#[gpui::test]
fn counting_is_not_none_and_too_many_is_not_a_total(cx: &mut TestAppContext) {
let (mut counting, _) = search(cx, HitCount::Counting);
assert_eq!(
counting.node("find.count").expect("published").value,
Some("counting".into())
);
let (mut none, _) = search(cx, HitCount::None);
assert_eq!(
none.node("find.count").expect("published").value,
Some("none".into())
);
let (mut many, _) = search(cx, HitCount::TooMany { counted: 500 });
let node = many.node("find.count").expect("published");
assert_eq!(node.value.as_deref(), Some("too-many"));
assert_ne!(node.value.as_deref(), Some("known"));
}
#[gpui::test]
fn a_search_that_found_nothing_cannot_be_stepped_through(cx: &mut TestAppContext) {
let (mut harness, _field) = search(cx, HitCount::None);
assert!(harness.node("find.next").expect("published").disabled);
assert!(harness.node("find.previous").expect("published").disabled);
}
#[gpui::test]
fn a_host_that_could_not_search_says_so_in_its_own_words(cx: &mut TestAppContext) {
let (mut harness, _field) = search(
cx,
HitCount::Unavailable("The index is still building.".into()),
);
let node = harness.node("find.count").expect("published");
assert_eq!(node.value.as_deref(), Some("unavailable"));
assert_eq!(node.text.as_deref(), Some("The index is still building."));
}
#[gpui::test]
fn stepping_reports_and_moves_nothing(cx: &mut TestAppContext) {
let (mut harness, field) = search(
cx,
HitCount::Known {
total: 12,
current: Some(2),
},
);
let (calls, sink) = recorder::<SearchFieldEvent>();
harness.update(move |_, cx| {
cx.subscribe(&field, move |_, event: &SearchFieldEvent, _| {
sink.borrow_mut().push(event.clone());
})
.detach();
});
harness.click("find.next");
assert_eq!(*calls.borrow(), vec![SearchFieldEvent::Next]);
assert_eq!(
harness
.node("find.count")
.expect("published")
.text
.as_deref(),
Some("3 of 12")
);
}
fn find_replace(cx: &mut TestAppContext, count: HitCount) -> (Harness, Calls<FindReplaceEvent>) {
let held: Rc<RefCell<Option<Entity<FindReplace>>>> = Rc::new(RefCell::new(None));
let sink = held.clone();
let mut harness = Harness::new(cx, gpui_kit::install, move |window, cx| {
let count = count.clone();
sink.borrow_mut()
.get_or_insert_with(|| {
cx.new(|cx| {
let mut surface = FindReplace::new("replace", window, cx);
surface.set_count(count, cx);
surface
})
})
.clone()
.into_any_element()
});
let surface = held.borrow().clone().expect("built");
let (calls, events) = recorder::<FindReplaceEvent>();
harness.update(move |_, cx| {
cx.subscribe(&surface, move |_, event: &FindReplaceEvent, _| {
events.borrow_mut().push(event.clone());
})
.detach();
});
(harness, calls)
}
#[gpui::test]
fn replace_all_states_its_count_before_it_is_taken(cx: &mut TestAppContext) {
let (mut harness, calls) = find_replace(
cx,
HitCount::Known {
total: 12,
current: Some(0),
},
);
let control = harness.node("replace.replace-all").expect("published");
assert_eq!(control.text.as_deref(), Some("Replace all 12"));
harness.click("replace.replace-all");
assert_eq!(
*calls.borrow(),
vec![FindReplaceEvent::ReplaceAll { count: 12 }]
);
}
#[gpui::test]
fn replace_all_refuses_a_count_nobody_established(cx: &mut TestAppContext) {
let (mut harness, calls) = find_replace(cx, HitCount::TooMany { counted: 500 });
assert!(
harness
.node("replace.replace-all")
.expect("published")
.disabled
);
assert!(harness.node("replace.replace-all.reason").is_some());
harness.click("replace.replace-all");
assert!(calls.borrow().is_empty());
}
#[gpui::test]
fn a_range_naming_nothing_real_costs_its_mark_and_not_the_line(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
HighlightedText::new("The transport reports what it did.")
.id("line")
.hits([4..13, 400..500])
.current(0)
.into_any_element()
});
let node = harness.node("line").expect("published");
assert_eq!(
node.text.as_deref(),
Some("The transport reports what it did.")
);
assert_eq!(node.value.as_deref(), Some("1"));
}
fn centre(cx: &mut TestAppContext) -> (Harness, Entity<NotificationCenter>) {
let held: Rc<RefCell<Option<Entity<NotificationCenter>>>> = Rc::new(RefCell::new(None));
let sink = held.clone();
let harness = Harness::new(cx, gpui_kit::install, move |_, cx| {
let centre = sink
.borrow_mut()
.get_or_insert_with(|| {
cx.new(|cx| {
let mut centre = NotificationCenter::new("notifications", cx);
centre.record(
Notification::new("run.exported", "Theme exported to disk")
.tone(Tone::Success)
.read(true),
cx,
);
centre.record(
Notification::new("run.refused", "The host refused to publish this run")
.tone(Tone::Danger),
cx,
);
centre
})
})
.clone();
centre.into_any_element()
});
let centre = held.borrow().clone().expect("built");
(harness, centre)
}
#[gpui::test]
fn the_badge_counts_only_what_the_centre_still_holds(cx: &mut TestAppContext) {
let (mut harness, centre) = centre(cx);
assert_eq!(
harness.node("notifications").expect("published").value,
Some("1".into())
);
harness.update(|_, cx| {
centre.update(cx, |centre, cx| centre.mark_all_read(cx));
});
assert_eq!(
harness.node("notifications").expect("published").value,
Some("0".into())
);
}
#[gpui::test]
fn a_centre_that_dropped_records_stops_claiming_an_exact_count(cx: &mut TestAppContext) {
let held: Rc<RefCell<Option<Entity<NotificationCenter>>>> = Rc::new(RefCell::new(None));
let sink = held.clone();
let mut harness = Harness::new(cx, gpui_kit::install, move |_, cx| {
sink.borrow_mut()
.get_or_insert_with(|| {
cx.new(|cx| NotificationCenter::new("notifications", cx).capacity(2))
})
.clone()
.into_any_element()
});
let centre = held.borrow().clone().expect("built");
harness.update(|_, cx| {
centre.update(cx, |centre, cx| {
for index in 0..3 {
centre.record(
Notification::new(format!("run.{index}"), format!("Run {index} finished")),
cx,
);
}
});
});
assert_eq!(
harness.update(|_, cx| centre.read(cx).unread()),
UnreadCount::AtLeast(2)
);
assert_eq!(
harness.node("notifications").expect("published").value,
Some("2+".into())
);
}
#[gpui::test]
fn dismissing_one_is_not_clearing_them_all(cx: &mut TestAppContext) {
let (mut harness, centre) = centre(cx);
harness.click("run.refused.dismiss");
assert!(harness.update(|_, cx| !centre.read(cx).holds("run.refused")));
assert!(harness.update(|_, cx| centre.read(cx).holds("run.exported")));
harness.click("notifications.clear-all");
assert!(harness.update(|_, cx| centre.read(cx).is_empty()));
}
#[gpui::test]
fn a_notification_publishes_its_severity_and_whether_it_was_read(cx: &mut TestAppContext) {
let (mut harness, _centre) = centre(cx);
let read = harness.node("run.exported").expect("published");
let unread = harness.node("run.refused").expect("published");
assert_eq!(read.checked, Some(true));
assert_eq!(unread.checked, Some(false));
assert_eq!(read.value.as_deref(), Some("success"));
assert_eq!(unread.value.as_deref(), Some("danger"));
}
#[gpui::test]
fn a_failed_panel_is_not_an_empty_one(cx: &mut TestAppContext) {
let (calls, sink) = recorder::<()>();
let mut harness = Harness::new(cx, gpui_kit::install, move |_, _| {
let sink = sink.clone();
let held: Result<(), &str> = Err("The workspace refused this query.");
div()
.children(FailurePanel::from_result("runs", &held).map(|panel| {
panel
.title("Runs")
.attempts(3)
.on_retry(move |_, _| sink.borrow_mut().push(()))
}))
.into_any_element()
});
let panel = harness.node("runs").expect("published");
assert_eq!(panel.value.as_deref(), Some("failed"));
assert!(panel.invalid);
assert_eq!(
harness
.node("runs.reason")
.expect("published")
.text
.as_deref(),
Some("The workspace refused this query.")
);
assert_eq!(
harness.node("runs.attempts").expect("published").value,
Some("3".into())
);
harness.click("runs.retry");
assert_eq!(calls.borrow().len(), 1);
}
#[gpui::test]
fn a_panel_with_nothing_to_retry_offers_no_control(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
FailurePanel::new("runs", "The workspace refused this query.").into_any_element()
});
assert!(harness.node("runs.retry").is_none());
}
fn code_lines() -> Vec<CodeLine> {
vec![
CodeLine::new(40, "fn report(&self) -> Outcome {"),
CodeLine::new(41, " let verified = self.check();").mark(LineMark::Added),
CodeLine::new(42, " let stale = self.cached();").mark(LineMark::Removed),
CodeLine::new(43, "}").mark(LineMark::Error),
]
}
#[gpui::test]
fn a_code_view_keeps_the_line_numbers_the_file_has(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
CodeView::new("hunk", code_lines())
.language("rust")
.into_any_element()
});
assert!(harness.node("hunk.lines.line-41").is_some());
assert!(harness.node("hunk.lines.line-1").is_none());
assert_eq!(
harness.node("hunk").expect("published").value,
Some("4".into())
);
}
#[gpui::test]
fn only_a_marked_line_is_an_assertion_target(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
CodeView::new("hunk", code_lines()).into_any_element()
});
assert!(harness.node("hunk.lines.line-40").is_none());
let added = harness.node("hunk.lines.line-41").expect("published");
let removed = harness.node("hunk.lines.line-42").expect("published");
let failing = harness.node("hunk.lines.line-43").expect("published");
assert_eq!(added.value.as_deref(), Some("added"));
assert_eq!(removed.value.as_deref(), Some("removed"));
assert_eq!(failing.value.as_deref(), Some("error"));
assert!(failing.invalid && !removed.invalid);
}
#[gpui::test]
fn a_code_view_lays_its_lines_out_one_below_the_next(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
CodeView::new("hunk", code_lines())
.language("rust")
.into_any_element()
});
let added = harness.bounds("hunk.lines.line-41").expect("laid out");
let removed = harness.bounds("hunk.lines.line-42").expect("laid out");
assert!(
added.size.height > gpui::px(0.0),
"a line that is on screen occupies space"
);
assert!(
removed.origin.y >= added.origin.y + added.size.height,
"line 42 sits below line 41 rather than on top of it"
);
}
#[gpui::test]
fn a_code_view_with_nothing_in_it_copies_nothing(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
CodeView::new("hunk", []).into_any_element()
});
assert!(harness.node("hunk.copy").expect("published").disabled);
assert!(harness.node("hunk.empty").is_some());
}
fn fixture_logs(count: usize) -> Vec<LogEntry> {
(0..count)
.map(|index| {
LogEntry::new(
format!("entry-{index:04}"),
format!("Fixture log message {index:04}"),
)
.timestamp(format!("09:41:{:02}", index % 60))
.level("INFO", Tone::Info)
.source("fixture")
})
.collect()
}
#[gpui::test]
fn a_log_stream_virtualizes_stable_entries_and_reports_intents(cx: &mut TestAppContext) {
let (selected, select_sink) = recorder::<String>();
let (copied, copy_sink) = recorder::<String>();
let mut harness = Harness::new(cx, gpui_kit::install, move |_, _| {
let select_sink = select_sink.clone();
let copy_sink = copy_sink.clone();
LogStream::new("logs", fixture_logs(1000))
.visible_rows(6)
.selected("entry-0999")
.on_select(move |id, _, _| select_sink.borrow_mut().push(id.to_string()))
.on_copy(move |id, _, _| copy_sink.borrow_mut().push(id.to_string()))
.into_any_element()
});
assert_eq!(
harness.node("logs.entries").expect("published").value,
Some("1000".into())
);
assert!(harness.node("logs.entries.entry-0999").is_some());
assert!(
harness.node("logs.entries.entry-0000").is_none(),
"following opens on the newest fixed rows"
);
harness.click("logs.entries.entry-0998");
harness.click("logs.copy");
assert_eq!(*selected.borrow(), vec!["entry-0998".to_string()]);
assert_eq!(*copied.borrow(), vec!["entry-0999".to_string()]);
assert!(
harness
.node("logs.entries.entry-0999")
.expect("published")
.selected,
"selection remains the caller's"
);
}
#[gpui::test]
fn pausing_a_log_changes_only_transient_follow_state(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
LogStream::new("logs", fixture_logs(8))
.visible_rows(4)
.into_any_element()
});
let following = harness.node("logs.mode").expect("published");
assert_eq!(following.value.as_deref(), Some("following"));
assert_eq!(
harness.node("logs.follow").expect("published").checked,
Some(true)
);
harness.click("logs.follow");
let paused = harness.node("logs.mode").expect("published");
assert_eq!(paused.value.as_deref(), Some("paused"));
assert_eq!(
harness.node("logs.follow").expect("published").checked,
Some(false)
);
assert_eq!(
harness.node("logs.entries").expect("still present").value,
Some("8".into())
);
}
#[gpui::test]
fn log_states_do_not_collapse_and_stale_keeps_verified_entries(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
div()
.column()
.children([
LogStream::new("loading", []).state(LogStreamState::Loading),
LogStream::new("empty", []).state(LogStreamState::Empty),
LogStream::new("unavailable", []).state(LogStreamState::Unavailable(
"The fixture source is offline.".into(),
)),
LogStream::new("error", []).state(LogStreamState::Error(
"The fixture response was unreadable.".into(),
)),
LogStream::new("stale", fixture_logs(2))
.state(LogStreamState::Stale("The fixture refresh failed.".into())),
])
.into_any_element()
});
for (id, state) in [
("loading", "loading"),
("empty", "empty"),
("unavailable", "unavailable"),
("error", "error"),
("stale", "stale"),
] {
assert_eq!(
harness.node(id).expect("published").value.as_deref(),
Some(state)
);
}
assert!(harness.node("loading").expect("published").busy);
assert!(harness.node("error").expect("published").invalid);
assert!(harness.node("stale.entries.entry-0001").is_some());
assert_eq!(
harness
.node("stale.stale")
.expect("published")
.text
.as_deref(),
Some("The fixture refresh failed.")
);
}
#[gpui::test]
fn log_search_hits_are_caller_ranges_and_payloads_are_not_published(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
LogStream::new(
"logs",
[LogEntry::new("match", "fixture ready")
.level("INFO", Tone::Info)
.search_hits([0..7, 40..50])],
)
.into_any_element()
});
assert_eq!(
harness
.node("logs.entries.match.hits")
.expect("published")
.value,
Some("1".into())
);
assert_eq!(
harness
.node("logs.entries.match")
.expect("published")
.text
.as_deref(),
Some("INFO"),
"the log payload does not enter diagnostic snapshots"
);
}
fn fixture_diff(lines: usize) -> Vec<DiffFile> {
let lines = (0..lines).map(|index| {
let line = DiffLine::new(
format!("line-{index:04}"),
format!("fixture line {index:04}"),
);
match index % 3 {
0 => line.old_number(index + 10).new_number(index + 10),
1 => DiffLine::added(
format!("line-{index:04}"),
format!("fixture line {index:04}"),
)
.new_number(index + 10),
_ => DiffLine::removed(
format!("line-{index:04}"),
format!("fixture line {index:04}"),
)
.old_number(index + 10),
}
});
vec![DiffFile::new(
"report",
"fixture/report.rs",
[DiffHunk::new("body", "@@ fixture @@", lines)],
)]
}
#[gpui::test]
fn a_diff_reports_file_hunk_and_line_identity_without_applying_anything(cx: &mut TestAppContext) {
let (events, sink) = recorder::<DiffViewEvent>();
let mut harness = Harness::new(cx, gpui_kit::install, move |_, _| {
let sink = sink.clone();
DiffView::new("review", fixture_diff(3))
.visible_rows(5)
.on_event(move |event, _, _| sink.borrow_mut().push(event))
.into_any_element()
});
harness.click("review.rows.file.report");
harness.click("review.rows.file.report.hunk.body");
harness.click("review.rows.file.report.hunk.body.line.line-0001");
assert_eq!(
events.borrow().as_slice(),
[
DiffViewEvent::FileActivated {
file_id: "report".into(),
},
DiffViewEvent::HunkActivated {
file_id: "report".into(),
hunk_id: "body".into(),
},
DiffViewEvent::LineActivated {
file_id: "report".into(),
hunk_id: "body".into(),
line_id: "line-0001".into(),
},
]
);
assert_eq!(
harness.node("review").expect("published").value.as_deref(),
Some("unified")
);
assert!(harness.node("review").expect("published").read_only);
}
#[gpui::test]
fn split_diff_uses_the_same_stable_rows_and_large_diffs_stay_virtual(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
DiffView::new("review", fixture_diff(1000))
.presentation(DiffPresentation::Split)
.visible_rows(6)
.on_event(|_, _, _| {})
.into_any_element()
});
assert_eq!(
harness.node("review").expect("published").value.as_deref(),
Some("split")
);
assert_eq!(
harness.node("review.rows").expect("published").value,
Some("1002".into())
);
assert!(
harness
.node("review.rows.file.report.hunk.body.line.line-0000")
.is_some()
);
assert!(
harness
.node("review.rows.file.report.hunk.body.line.line-0900")
.is_none(),
"a far line is neither laid out nor published"
);
}
#[gpui::test]
fn sparkline_publishes_the_callers_exact_reading_and_range(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
Sparkline::new(
"throughput",
"Fixture throughput",
SparklineState::Ready(SparklineReading::new(
[
SparklinePoint::new(0.0, 0.2),
SparklinePoint::new(0.5, 0.7),
SparklinePoint::new(1.0, 0.4),
],
"40 req/s",
"20 req/s",
"70 req/s",
)),
)
.into_any_element()
});
let node = harness.node("throughput").expect("published");
assert_eq!(node.role, Role::Image);
assert_eq!(node.text.as_deref(), Some("Fixture throughput"));
assert_eq!(node.value.as_deref(), Some("40 req/s"));
assert_eq!(
node.description.as_deref(),
Some("Minimum 20 req/s; maximum 70 req/s")
);
assert!(node.read_only);
}
#[gpui::test]
fn sparkline_states_are_distinct_and_stale_keeps_the_verified_reading(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
div()
.column()
.children([
Sparkline::new("loading", "Loading metric", SparklineState::Loading),
Sparkline::new("empty", "Empty metric", SparklineState::Empty),
Sparkline::new(
"unavailable",
"Unavailable metric",
SparklineState::Unavailable("The fixture has no sampler.".into()),
),
Sparkline::new(
"error",
"Failed metric",
SparklineState::Error("The fixture response was unreadable.".into()),
),
Sparkline::new(
"stale",
"Stale metric",
SparklineState::Stale {
reading: SparklineReading::new(
[SparklinePoint::new(0.0, 0.2), SparklinePoint::new(1.0, 0.8)],
"8 jobs",
"2 jobs",
"9 jobs",
),
reason: "The fixture refresh failed.".into(),
},
),
])
.into_any_element()
});
assert!(harness.node("loading").expect("published").busy);
assert_eq!(
harness.node("loading").expect("published").value.as_deref(),
Some("loading")
);
assert_eq!(
harness.node("empty").expect("published").value.as_deref(),
Some("empty")
);
assert_eq!(
harness
.node("unavailable")
.expect("published")
.value
.as_deref(),
Some("unavailable")
);
let error = harness.node("error").expect("published");
assert!(error.invalid);
assert_eq!(error.value.as_deref(), Some("error"));
assert_eq!(
error.description.as_deref(),
Some("The fixture response was unreadable.")
);
assert_eq!(
harness.node("stale").expect("published").value.as_deref(),
Some("8 jobs")
);
assert_eq!(
harness
.node("stale.stale")
.expect("published")
.value
.as_deref(),
Some("stale")
);
}
fn uploads() -> Vec<Upload> {
vec![
Upload::new("brief", "brief.pdf").done(),
Upload::new("capture", "capture.png").uploading(0.4),
Upload::new("archive", "archive.zip").failed("The connection dropped."),
Upload::new("installer", "installer.exe").refused("This zone does not take programs."),
]
}
fn upload_list(cx: &mut TestAppContext) -> (Harness, Calls<SharedString>) {
let (calls, sink) = recorder::<SharedString>();
let harness = Harness::new(cx, gpui_kit::install, move |_, _| {
let sink = sink.clone();
UploadList::new("attachments")
.uploads(uploads())
.on_retry(move |id, _, _| sink.borrow_mut().push(id))
.on_cancel(|_, _, _| {})
.on_remove(|_, _, _| {})
.into_any_element()
});
(harness, calls)
}
#[gpui::test]
fn a_refusal_is_not_a_failure(cx: &mut TestAppContext) {
let (mut harness, _calls) = upload_list(cx);
let failed = harness.node("attachments.archive").expect("published");
let refused = harness.node("attachments.installer").expect("published");
assert_eq!(failed.value.as_deref(), Some("failed"));
assert_eq!(refused.value.as_deref(), Some("refused"));
assert!(failed.invalid);
assert!(!refused.invalid && refused.disabled);
}
#[gpui::test]
fn only_a_failure_is_offered_a_retry(cx: &mut TestAppContext) {
let (mut harness, calls) = upload_list(cx);
assert!(harness.node("attachments.installer.retry").is_none());
assert!(harness.node("attachments.brief.retry").is_none());
harness.click("attachments.archive.retry");
assert_eq!(*calls.borrow(), vec![SharedString::from("archive")]);
}
#[gpui::test]
fn a_file_in_flight_can_be_stopped_and_a_settled_one_removed(cx: &mut TestAppContext) {
let (mut harness, _calls) = upload_list(cx);
assert!(harness.node("attachments.capture.cancel").is_some());
assert!(harness.node("attachments.capture.remove").is_none());
assert!(harness.node("attachments.brief.remove").is_some());
assert!(harness.node("attachments.brief.cancel").is_none());
}
#[gpui::test]
fn a_batch_with_an_unknown_extent_shows_no_percentage(cx: &mut TestAppContext) {
let mut harness = Harness::new(cx, gpui_kit::install, |_, _| {
UploadList::new("attachments")
.uploads([
Upload::new("a", "a.bin").uploading(None),
Upload::new("b", "b.bin").done(),
])
.into_any_element()
});
let bar = harness.node("attachments.overall").expect("published");
assert_eq!(bar.value_now, None);
}
#[gpui::test]
fn a_disabled_list_installs_no_handler_at_all(cx: &mut TestAppContext) {
let (calls, sink) = recorder::<SharedString>();
let mut harness = Harness::new(cx, gpui_kit::install, move |_, _| {
let sink = sink.clone();
UploadList::new("attachments")
.uploads(uploads())
.on_retry(move |id, _, _| sink.borrow_mut().push(id))
.disabled(true)
.into_any_element()
});
assert!(harness.node("attachments").expect("published").disabled);
assert!(harness.node("attachments.archive.retry").is_none());
assert!(calls.borrow().is_empty());
}