Eric HolkFix CI test failuresb380a037 months agoHistory
8186 lines · 279.9 KB · Rust
RawDownload
use super::*;use acp_thread::{AcpThread, PermissionOptions, StubAgentConnection};use agent::ThreadStore;use agent_ui::{    test_support::{active_session_id, open_thread_with_connection, send_message},    thread_metadata_store::ThreadMetadata,};use chrono::DateTime;use fs::{FakeFs, Fs};use gpui::TestAppContext;use pretty_assertions::assert_eq;use project::AgentId;use settings::SettingsStore;use std::{    path::{Path, PathBuf},    sync::Arc,};use util::path_list::PathList; fn init_test(cx: &mut TestAppContext) {    cx.update(|cx| {        let settings_store = SettingsStore::test(cx);        cx.set_global(settings_store);        theme_settings::init(theme::LoadThemes::JustBase, cx);        editor::init(cx);        ThreadStore::init_global(cx);        ThreadMetadataStore::init_global(cx);        language_model::LanguageModelRegistry::test(cx);        prompt_store::init(cx);    });} #[track_caller]fn assert_active_thread(sidebar: &Sidebar, session_id: &acp::SessionId, msg: &str) {    assert!(        sidebar            .active_entry            .as_ref()            .is_some_and(|e| e.is_active_thread(session_id)),        "{msg}: expected active_entry to be Thread({session_id:?}), got {:?}",        sidebar.active_entry,    );} #[track_caller]fn assert_active_draft(sidebar: &Sidebar, workspace: &Entity<Workspace>, msg: &str) {    assert!(        matches!(&sidebar.active_entry, Some(ActiveEntry::Draft(ws)) if ws == workspace),        "{msg}: expected active_entry to be Draft for workspace {:?}, got {:?}",        workspace.entity_id(),        sidebar.active_entry,    );} fn has_thread_entry(sidebar: &Sidebar, session_id: &acp::SessionId) -> bool {    sidebar        .contents        .entries        .iter()        .any(|entry| matches!(entry, ListEntry::Thread(t) if &t.metadata.session_id == session_id))} #[track_caller]fn assert_remote_project_integration_sidebar_state(    sidebar: &mut Sidebar,    main_thread_id: &acp::SessionId,    remote_thread_id: &acp::SessionId,) {    let mut project_headers = sidebar.contents.entries.iter().filter_map(|entry| {        if let ListEntry::ProjectHeader { label, .. } = entry {            Some(label.as_ref())        } else {            None        }    });     let Some(project_header) = project_headers.next() else {        panic!("expected exactly one sidebar project header named `project`, found none");    };    assert_eq!(        project_header, "project",        "expected the only sidebar project header to be `project`"    );    if let Some(unexpected_header) = project_headers.next() {        panic!(            "expected exactly one sidebar project header named `project`, found extra header `{unexpected_header}`"        );    }     let mut saw_main_thread = false;    let mut saw_remote_thread = false;    for entry in &sidebar.contents.entries {        match entry {            ListEntry::ProjectHeader { label, .. } => {                assert_eq!(                    label.as_ref(),                    "project",                    "expected the only sidebar project header to be `project`"                );            }            ListEntry::Thread(thread) if &thread.metadata.session_id == main_thread_id => {                saw_main_thread = true;            }            ListEntry::Thread(thread) if &thread.metadata.session_id == remote_thread_id => {                saw_remote_thread = true;            }            ListEntry::Thread(thread) => {                let title = thread.metadata.title.as_ref();                panic!(                    "unexpected sidebar thread while simulating remote project integration flicker: title=`{title}`"                );            }            ListEntry::ViewMore { .. } => {                panic!(                    "unexpected `View More` entry while simulating remote project integration flicker"                );            }            ListEntry::DraftThread { .. } => {}        }    }     assert!(        saw_main_thread,        "expected the sidebar to keep showing `Main Thread` under `project`"    );    assert!(        saw_remote_thread,        "expected the sidebar to keep showing `Worktree Thread` under `project`"    );} async fn init_test_project(    worktree_path: &str,    cx: &mut TestAppContext,) -> Entity<project::Project> {    init_test(cx);    let fs = FakeFs::new(cx.executor());    fs.insert_tree(worktree_path, serde_json::json!({ "src": {} }))        .await;    cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));    project::Project::test(fs, [worktree_path.as_ref()], cx).await} fn setup_sidebar(    multi_workspace: &Entity<MultiWorkspace>,    cx: &mut gpui::VisualTestContext,) -> Entity<Sidebar> {    let sidebar = setup_sidebar_closed(multi_workspace, cx);    multi_workspace.update_in(cx, |mw, window, cx| {        mw.toggle_sidebar(window, cx);    });    cx.run_until_parked();    sidebar} fn setup_sidebar_closed(    multi_workspace: &Entity<MultiWorkspace>,    cx: &mut gpui::VisualTestContext,) -> Entity<Sidebar> {    let multi_workspace = multi_workspace.clone();    let sidebar =        cx.update(|window, cx| cx.new(|cx| Sidebar::new(multi_workspace.clone(), window, cx)));    multi_workspace.update(cx, |mw, cx| {        mw.register_sidebar(sidebar.clone(), cx);    });    cx.run_until_parked();    sidebar} async fn save_n_test_threads(    count: u32,    project: &Entity<project::Project>,    cx: &mut gpui::VisualTestContext,) {    for i in 0..count {        save_thread_metadata(            acp::SessionId::new(Arc::from(format!("thread-{}", i))),            format!("Thread {}", i + 1).into(),            chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, i).unwrap(),            None,            project,            cx,        )    }    cx.run_until_parked();} async fn save_test_thread_metadata(    session_id: &acp::SessionId,    project: &Entity<project::Project>,    cx: &mut TestAppContext,) {    save_thread_metadata(        session_id.clone(),        "Test".into(),        chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 0).unwrap(),        None,        project,        cx,    )} async fn save_named_thread_metadata(    session_id: &str,    title: &str,    project: &Entity<project::Project>,    cx: &mut gpui::VisualTestContext,) {    save_thread_metadata(        acp::SessionId::new(Arc::from(session_id)),        SharedString::from(title.to_string()),        chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 0).unwrap(),        None,        project,        cx,    );    cx.run_until_parked();} fn save_thread_metadata(    session_id: acp::SessionId,    title: SharedString,    updated_at: DateTime<Utc>,    created_at: Option<DateTime<Utc>>,    project: &Entity<project::Project>,    cx: &mut TestAppContext,) {    cx.update(|cx| {        let (folder_paths, main_worktree_paths) = {            let project_ref = project.read(cx);            let paths: Vec<Arc<Path>> = project_ref                .visible_worktrees(cx)                .map(|worktree| worktree.read(cx).abs_path())                .collect();            let folder_paths = PathList::new(&paths);            let main_worktree_paths = project_ref.project_group_key(cx).path_list().clone();            (folder_paths, main_worktree_paths)        };        let metadata = ThreadMetadata {            session_id,            agent_id: agent::ZED_AGENT_ID.clone(),            title,            updated_at,            created_at,            folder_paths,            main_worktree_paths,            archived: false,        };        ThreadMetadataStore::global(cx).update(cx, |store, cx| store.save_manually(metadata, cx));    });    cx.run_until_parked();} fn save_thread_metadata_with_main_paths(    session_id: &str,    title: &str,    folder_paths: PathList,    main_worktree_paths: PathList,    cx: &mut TestAppContext,) {    let session_id = acp::SessionId::new(Arc::from(session_id));    let title = SharedString::from(title.to_string());    let updated_at = chrono::TimeZone::with_ymd_and_hms(&Utc, 2024, 1, 1, 0, 0, 0).unwrap();    let metadata = ThreadMetadata {        session_id,        agent_id: agent::ZED_AGENT_ID.clone(),        title,        updated_at,        created_at: None,        folder_paths,        main_worktree_paths,        archived: false,    };    cx.update(|cx| {        ThreadMetadataStore::global(cx).update(cx, |store, cx| store.save_manually(metadata, cx));    });    cx.run_until_parked();} fn focus_sidebar(sidebar: &Entity<Sidebar>, cx: &mut gpui::VisualTestContext) {    sidebar.update_in(cx, |_, window, cx| {        cx.focus_self(window);    });    cx.run_until_parked();} fn request_test_tool_authorization(    thread: &Entity<AcpThread>,    tool_call_id: &str,    option_id: &str,    cx: &mut gpui::VisualTestContext,) {    let tool_call_id = acp::ToolCallId::new(tool_call_id);    let label = format!("Tool {tool_call_id}");    let option_id = acp::PermissionOptionId::new(option_id);    let _authorization_task = cx.update(|_, cx| {        thread.update(cx, |thread, cx| {            thread                .request_tool_call_authorization(                    acp::ToolCall::new(tool_call_id, label)                        .kind(acp::ToolKind::Edit)                        .into(),                    PermissionOptions::Flat(vec![acp::PermissionOption::new(                        option_id,                        "Allow",                        acp::PermissionOptionKind::AllowOnce,                    )]),                    cx,                )                .unwrap()        })    });    cx.run_until_parked();} fn format_linked_worktree_chips(worktrees: &[WorktreeInfo]) -> String {    let mut seen = Vec::new();    let mut chips = Vec::new();    for wt in worktrees {        if wt.kind == ui::WorktreeKind::Main {            continue;        }        if !seen.contains(&wt.name) {            seen.push(wt.name.clone());            chips.push(format!("{{{}}}", wt.name));        }    }    if chips.is_empty() {        String::new()    } else {        format!(" {}", chips.join(", "))    }} fn visible_entries_as_strings(    sidebar: &Entity<Sidebar>,    cx: &mut gpui::VisualTestContext,) -> Vec<String> {    sidebar.read_with(cx, |sidebar, _cx| {        sidebar            .contents            .entries            .iter()            .enumerate()            .map(|(ix, entry)| {                let selected = if sidebar.selection == Some(ix) {                    "  <== selected"                } else {                    ""                };                let is_active = sidebar                    .active_entry                    .as_ref()                    .is_some_and(|active| active.matches_entry(entry));                let active_indicator = if is_active { " (active)" } else { "" };                match entry {                    ListEntry::ProjectHeader {                        label,                        key,                        highlight_positions: _,                        ..                    } => {                        let icon = if sidebar.collapsed_groups.contains(key) {                            ">"                        } else {                            "v"                        };                        format!("{} [{}]{}", icon, label, selected)                    }                    ListEntry::Thread(thread) => {                        let title = thread.metadata.title.as_ref();                        let live = if thread.is_live { " *" } else { "" };                        let status_str = match thread.status {                            AgentThreadStatus::Running => " (running)",                            AgentThreadStatus::Error => " (error)",                            AgentThreadStatus::WaitingForConfirmation => " (waiting)",                            _ => "",                        };                        let notified = if sidebar                            .contents                            .is_thread_notified(&thread.metadata.session_id)                        {                            " (!)"                        } else {                            ""                        };                        let worktree = format_linked_worktree_chips(&thread.worktrees);                        format!("  {title}{worktree}{live}{status_str}{notified}{active_indicator}{selected}")                    }                    ListEntry::ViewMore {                        is_fully_expanded, ..                    } => {                        if *is_fully_expanded {                            format!("  - Collapse{}", selected)                        } else {                            format!("  + View More{}", selected)                        }                    }                    ListEntry::DraftThread {                        workspace,                        worktrees,                        ..                    } => {                        let worktree = format_linked_worktree_chips(worktrees);                        if workspace.is_some() {                            format!("  [+ New Thread{}]{}", worktree, selected)                        } else {                            format!("  [~ Draft{}]{}{}", worktree, active_indicator, selected)                        }                    }                }            })            .collect()    })} #[gpui::test]async fn test_serialization_round_trip(cx: &mut TestAppContext) {    let project = init_test_project("/my-project", cx).await;    let (multi_workspace, cx) =        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));    let sidebar = setup_sidebar(&multi_workspace, cx);     save_n_test_threads(3, &project, cx).await;     let project_group_key = project.read_with(cx, |project, cx| project.project_group_key(cx));     // Set a custom width, collapse the group, and expand "View More".    sidebar.update_in(cx, |sidebar, window, cx| {        sidebar.set_width(Some(px(420.0)), cx);        sidebar.toggle_collapse(&project_group_key, window, cx);        sidebar.expanded_groups.insert(project_group_key.clone(), 2);    });    cx.run_until_parked();     // Capture the serialized state from the first sidebar.    let serialized = sidebar.read_with(cx, |sidebar, cx| sidebar.serialized_state(cx));    let serialized = serialized.expect("serialized_state should return Some");     // Create a fresh sidebar and restore into it.    let sidebar2 =        cx.update(|window, cx| cx.new(|cx| Sidebar::new(multi_workspace.clone(), window, cx)));    cx.run_until_parked();     sidebar2.update_in(cx, |sidebar, window, cx| {        sidebar.restore_serialized_state(&serialized, window, cx);    });    cx.run_until_parked();     // Assert all serialized fields match.    let (width1, collapsed1, expanded1) = sidebar.read_with(cx, |s, _| {        (            s.width,            s.collapsed_groups.clone(),            s.expanded_groups.clone(),        )    });    let (width2, collapsed2, expanded2) = sidebar2.read_with(cx, |s, _| {        (            s.width,            s.collapsed_groups.clone(),            s.expanded_groups.clone(),        )    });     assert_eq!(width1, width2);    assert_eq!(collapsed1, collapsed2);    assert_eq!(expanded1, expanded2);    assert_eq!(width1, px(420.0));    assert!(collapsed1.contains(&project_group_key));    assert_eq!(expanded1.get(&project_group_key), Some(&2));} #[gpui::test]async fn test_restore_serialized_archive_view_does_not_panic(cx: &mut TestAppContext) {    // A regression test to ensure that restoring a serialized archive view does not panic.    let project = init_test_project_with_agent_panel("/my-project", cx).await;    let (multi_workspace, cx) =        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));    let (sidebar, _panel) = setup_sidebar_with_agent_panel(&multi_workspace, cx);    cx.update(|_window, cx| {        AgentRegistryStore::init_test_global(cx, vec![]);    });     let serialized = serde_json::to_string(&SerializedSidebar {        width: Some(400.0),        collapsed_groups: Vec::new(),        expanded_groups: Vec::new(),        active_view: SerializedSidebarView::Archive,    })    .expect("serialization should succeed");     multi_workspace.update_in(cx, |multi_workspace, window, cx| {        if let Some(sidebar) = multi_workspace.sidebar() {            sidebar.restore_serialized_state(&serialized, window, cx);        }    });    cx.run_until_parked();     // After the deferred `show_archive` runs, the view should be Archive.

Lines 1–500 of 8186.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t