T24c: Agent, changes
- FileChangeCard: a proposed change to one file, its path and counts, Reject and Accept in a slot that keeps presses off the header; opened, the diff through a headless git::DiffViewer with its folds opening - MultiFileDiffReview: every file at once, the total counts, how many are reviewed, Reject all and Accept all; verdicts as collab::Decision - git::DiffViewer gains headless, leaving the file's header to its owner - CommandExecutionCard is terminal::CommandBlock - gallery: three proposed files and a command's block
This commit is contained in:
1 parent
859a9051d8
commit
0a44a2d905
9 files changed
+591
-26
No files matched your search
@@ -0,0 +1,158 @@
|
||||
use gpui::{
|
||||
Context, Entity, IntoElement, KeyBinding, KeyUpEvent, Keystroke, ParentElement, Render, Styled,
|
||||
TestAppContext, VisualTestContext, Window, div,
|
||||
};
|
||||
|
||||
mod changes;
|
||||
|
||||
use super::{AgentProgress, Permission, PermissionPrompt, ToolApprovalDialog};
|
||||
use crate::{forms, primitives::FocusNext, theme::Theme};
|
||||
|
||||
/// An approval dialog while open, and the answers it gave.
|
||||
struct Asking {
|
||||
open: bool,
|
||||
answers: Vec<bool>,
|
||||
}
|
||||
|
||||
impl Render for Asking {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.entity();
|
||||
div().size_full().children(self.open.then(|| {
|
||||
ToolApprovalDialog::new(
|
||||
"approve",
|
||||
"write_file",
|
||||
r#"{ "path": "lift.rs" }"#,
|
||||
move |approved, _, cx| {
|
||||
view.update(cx, |asking, cx| {
|
||||
asking.answers.push(approved);
|
||||
asking.open = false;
|
||||
cx.notify();
|
||||
})
|
||||
},
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn settle(cx: &mut VisualTestContext) {
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, _| window.refresh());
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
/// Presses and releases `key`, a frame apart.
|
||||
pub(super) fn press(key: &str, cx: &mut VisualTestContext) {
|
||||
cx.simulate_keystrokes(key);
|
||||
settle(cx);
|
||||
cx.simulate_event(KeyUpEvent {
|
||||
keystroke: Keystroke::parse(key).expect("a key"),
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
fn asking(cx: &mut TestAppContext) -> (Entity<Asking>, &mut VisualTestContext) {
|
||||
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(|_, _| Asking {
|
||||
open: true,
|
||||
answers: Vec::new(),
|
||||
});
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
(view, cx)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn escape_denies_the_call(cx: &mut TestAppContext) {
|
||||
let (view, cx) = asking(cx);
|
||||
press("escape", cx);
|
||||
assert_eq!(
|
||||
view.read_with(cx, |asking, _| asking.answers.clone()),
|
||||
[false]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn run_approves_the_call_once(cx: &mut TestAppContext) {
|
||||
let (view, cx) = asking(cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
view.read_with(cx, |asking, _| asking.answers.clone()),
|
||||
[true]
|
||||
);
|
||||
}
|
||||
|
||||
/// A permission prompt and the answers it gave.
|
||||
struct Prompting {
|
||||
answers: Vec<Permission>,
|
||||
}
|
||||
|
||||
impl Render for Prompting {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.entity();
|
||||
div().w_full().child(
|
||||
PermissionPrompt::new("permission", "Run commands here?", move |answer, _, cx| {
|
||||
view.update(cx, |prompting, _| prompting.answers.push(answer))
|
||||
})
|
||||
.detail("cargo test"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_prompt_walks_deny_always_then_once(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
forms::bind_keys(cx);
|
||||
});
|
||||
let (view, cx) = cx.add_window_view(|_, _| Prompting {
|
||||
answers: Vec::new(),
|
||||
});
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
for _ in 0..3 {
|
||||
cx.update(|window, _| window.focus_next());
|
||||
}
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
view.read_with(cx, |prompting, _| prompting.answers.clone()),
|
||||
[Permission::Once]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "progress of 6 in 5")]
|
||||
fn progress_past_its_total_fails_loud() {
|
||||
let _ = AgentProgress::new("progress", "Writing", 6, 5);
|
||||
}
|
||||
|
||||
/// A progress bar for a task of one step, `done` or not.
|
||||
struct Single {
|
||||
done: usize,
|
||||
}
|
||||
|
||||
impl Render for Single {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.w_full()
|
||||
.child(AgentProgress::new("single", "The one step", self.done, 1))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_task_of_one_step_draws_before_and_after(cx: &mut TestAppContext) {
|
||||
cx.update(Theme::init);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Single { done: 0 });
|
||||
settle(cx);
|
||||
view.update(cx, |single, cx| {
|
||||
single.done = 1;
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
Reference in new issue
Block a user