use gpui::{ Context, IntoElement, KeyBinding, KeyUpEvent, Keystroke, ParentElement, Render, Styled, TestAppContext, VisualTestContext, Window, div, px, }; use super::DiffViewer; use crate::{forms, primitives::FocusNext, theme::Theme}; /// A diff whose first stretch folds, and the folds asked open. struct Folded { opened: Vec, } impl Render for Folded { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let old: String = (0..20).map(|ix| format!("line {ix}\n")).collect(); let new = old.replace("line 18", "line eighteen"); div().w(px(480.0)).h(px(320.0)).child( DiffViewer::new("diff", "a.rs", &old, &new) .headless() .on_open(move |ix, _, cx| view.update(cx, |folded, _| folded.opened.push(ix))), ) } } fn settle(cx: &mut VisualTestContext) { cx.run_until_parked(); cx.update(|window, _| window.refresh()); cx.run_until_parked(); } #[gpui::test] fn a_fold_opens_from_the_keyboard(cx: &mut TestAppContext) { cx.update(|cx| { Theme::init(cx); forms::bind_keys(cx); cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]); }); let (view, cx) = cx.add_window_view(|_, _| Folded { opened: Vec::new() }); cx.update(|window, _| window.activate_window()); settle(cx); cx.update(|window, cx| window.focus_next(cx)); settle(cx); cx.simulate_keystrokes("enter"); settle(cx); cx.simulate_event(KeyUpEvent { keystroke: Keystroke::parse("enter").expect("a key"), }); settle(cx); assert_eq!(view.read_with(cx, |folded, _| folded.opened.clone()), [0]); } /// A blame from before the file changed: an owner too few, and one past the blames. struct Behind; impl Render for Behind { fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { let blame = super::Blame { commit: "a1".into(), author: "Ann".into(), when: "today".into(), subject: "first".into(), age: 0.0, }; div().w(px(600.0)).child( super::BlameView::new("blame", "one\ntwo\nthree\nfour", [0, 3, 0], [blame]).current(3), ) } } #[gpui::test] fn a_blame_that_lags_its_code_draws_every_line(cx: &mut TestAppContext) { cx.update(Theme::init); let (_, cx) = cx.add_window_view(|_, _| Behind); cx.run_until_parked(); } /// Every section of a changes list, each with one file. struct Sorted; impl Render for Sorted { fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { use crate::lists::GitStatus; let file = |path: &str, status| super::Changed { path: path.to_string().into(), status, lines: Some((1, 0)), }; div().w(px(420.0)).child( super::ChangesList::new( "changes", [file("a.rs", GitStatus::Modified)], [file("b.rs", GitStatus::Modified)], ) .conflicted([file("c.rs", GitStatus::Conflicted)]) .untracked([file("d.rs", GitStatus::Untracked)]) .ignored([super::Changed { lines: None, ..file("target/", GitStatus::Untracked) }]), ) } } #[gpui::test] fn every_section_draws_with_its_own_words(cx: &mut TestAppContext) { cx.update(Theme::init); let (_, cx) = cx.add_window_view(|_, _| Sorted); settle(cx); } /// Lines and hunks pressed, from stretches made elsewhere. #[derive(Default)] struct Picking { lines: Vec<((usize, usize), bool)>, hunks: Vec, } impl Render for Picking { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let (lines, hunks) = (cx.entity(), cx.entity()); let stretches = std::rc::Rc::new(super::diff("a\nb\n", "a\nc\n", 1)); div().w(px(480.0)).h(px(320.0)).child( DiffViewer::from_stretches("picking", "a.txt", stretches) .selected([(0, 2)]) .on_line(move |place, shift, _, cx| { lines.update(cx, |picking, _| picking.lines.push((place, shift))) }) .hunk_action("Stage", move |hunk, _, cx| { hunks.update(cx, |picking, _| picking.hunks.push(hunk)) }), ) } } #[gpui::test] fn changed_lines_and_hunks_answer_presses(cx: &mut TestAppContext) { cx.update(|cx| { Theme::init(cx); forms::bind_keys(cx); cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]); }); let (view, cx) = cx.add_window_view(|_, _| Picking::default()); cx.update(|window, _| window.activate_window()); settle(cx); let press = |cx: &mut VisualTestContext, key: &str| { cx.simulate_keystrokes(key); settle(cx); cx.simulate_event(KeyUpEvent { keystroke: Keystroke::parse(key).expect("a key"), }); settle(cx); }; // Past the two layout segments: the hunk's button, then its lines. let next = |cx: &mut VisualTestContext| { cx.update(|window, cx| window.focus_next(cx)); settle(cx); }; (0..3).for_each(|_| next(cx)); press(cx, "enter"); next(cx); press(cx, "enter"); next(cx); press(cx, "enter"); let (lines, hunks) = view.read_with(cx, |picking, _| { (picking.lines.clone(), picking.hunks.clone()) }); assert_eq!(hunks, [0], "the hunk's button"); assert_eq!( lines, [((0, 1), false), ((0, 2), false)], "removed, then added" ); }