use pretty_assertions::assert_eq;
use tempfile::tempdir;
use crate::tools::mcp::McpResource;
use super::{
super::{tests::test_app, ComposerAttachment, PendingAttachmentSource},
FilePaletteEntry,
};
#[derive(Debug, PartialEq, Eq)]
enum SelectionEffect {
Reference(String),
PendingResource { text: String, name: String },
}
fn resource(uri: &str, templated: bool) -> McpResource {
McpResource {
server: "docs".into(),
uri: uri.into(),
name: "doc".into(),
title: None,
description: None,
mime_type: None,
templated,
}
}
fn select(entry: FilePaletteEntry) -> SelectionEffect {
let mut app = test_app();
app.insert_pasted_input_text("look at @doc");
app.apply_file_palette_selection(&entry).unwrap();
match app.input_ui.attachments().as_slice() {
[] => SelectionEffect::Reference(app.input_ui.text().to_string()),
[ComposerAttachment::Pending {
source: PendingAttachmentSource::McpResource,
name,
..
}] => SelectionEffect::PendingResource {
text: app.input_ui.text().to_string(),
name: name.clone(),
},
other => panic!("unexpected composer attachments: {other:?}"),
}
}
#[tokio::test]
async fn selection_inserts_names_as_text_and_attaches_content() {
let cases = [
(
"a workspace file is still only a path",
FilePaletteEntry::WorkspaceFile("src/lib.rs".into()),
SelectionEffect::Reference("look at @src/lib.rs ".into()),
),
(
"a template cannot be read, so the user fills it in",
FilePaletteEntry::McpResource(resource("res://users/{id}", true)),
SelectionEffect::Reference("look at @res://users/{id} ".into()),
),
(
"a concrete resource is pulled in, mention and all",
FilePaletteEntry::McpResource(resource("res://doc", false)),
SelectionEffect::PendingResource {
text: "look at ".into(),
name: "res://doc".into(),
},
),
];
for (name, entry, expected) in cases {
assert_eq!(select(entry), expected, "{name}");
}
}
#[test]
fn expired_app_file_cache_rediscovers_workspace_files() {
let workspace = tempdir().unwrap();
std::fs::write(workspace.path().join("old.rs"), "").unwrap();
let mut app = test_app();
app.info.runtime.cwd = workspace.path().to_path_buf();
app.input_ui.set_text("@".to_string());
app.input_ui.set_cursor(1);
let first = app.file_match_list();
assert_eq!(first.len(), 1);
std::fs::write(workspace.path().join("new.rs"), "").unwrap();
app.palette_caches.expire_file();
assert_eq!(
app.file_match_list().len(),
1,
"an expired match cache must still reuse a fresh workspace walk"
);
app.palette_caches.expire_file();
app.palette_caches.expire_workspace();
let refreshed = app.file_match_list();
assert_eq!(refreshed.len(), 2);
}
#[test]
fn different_queries_reuse_workspace_walk_until_expired() {
let workspace = tempdir().unwrap();
std::fs::write(workspace.path().join("src.rs"), "").unwrap();
let mut app = test_app();
app.info.runtime.cwd = workspace.path().to_path_buf();
app.input_ui.set_text("@s".to_string());
app.input_ui.set_cursor(2);
assert_eq!(app.file_match_list().len(), 1);
std::fs::write(workspace.path().join("lib.rs"), "").unwrap();
app.input_ui.set_text("@".to_string());
app.input_ui.set_cursor(1);
assert_eq!(
app.file_match_list().len(),
1,
"a different query must reuse the walk, not rediscover"
);
app.palette_caches.expire_file();
app.palette_caches.expire_workspace();
assert_eq!(app.file_match_list().len(), 2);
}