aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src/comp
diff options
context:
space:
mode:
authorWeicao-CatilGrass <1992414357@qq.com>2026-06-09 21:08:20 +0800
committerWeicao-CatilGrass <1992414357@qq.com>2026-06-09 22:23:16 +0800
commit514929c3b8ee0d4f540be5eb4bc8c1a10e62095d (patch)
tree8faeeb71075a695354496af38eb527085bb37f92 /mingling_core/src/comp
parent92cccd9517e764508dfa0342ae2ea254661d0a8f (diff)
Add unit and integration tests for mingling_core
Diffstat (limited to 'mingling_core/src/comp')
-rw-r--r--mingling_core/src/comp/comp_ctx.rs80
-rw-r--r--mingling_core/src/comp/shell_ctx.rs125
-rw-r--r--mingling_core/src/comp/suggest.rs263
3 files changed, 468 insertions, 0 deletions
diff --git a/mingling_core/src/comp/comp_ctx.rs b/mingling_core/src/comp/comp_ctx.rs
index 02e79c5..b9f9020 100644
--- a/mingling_core/src/comp/comp_ctx.rs
+++ b/mingling_core/src/comp/comp_ctx.rs
@@ -17,3 +17,83 @@ where
.is_some_and(|arg| arg == COMPLETION_SUBCOMMAND)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{AnyOutput, ChainProcess, Groupped, RenderResult};
+
+ /// Minimal mock collector that satisfies `C: ProgramCollect<Enum = C>`
+ /// by setting `Enum = Self`.
+ #[derive(Debug, Clone, PartialEq)]
+ struct MockCollect;
+
+ impl Groupped<MockCollect> for MockCollect {
+ fn member_id() -> MockCollect {
+ MockCollect
+ }
+ }
+
+ impl ProgramCollect for MockCollect {
+ type Enum = MockCollect;
+ type ErrorDispatcherNotFound = MockCollect;
+ type ErrorRendererNotFound = MockCollect;
+ type ResultEmpty = MockCollect;
+
+ fn build_renderer_not_found(_member_id: MockCollect) -> AnyOutput<MockCollect> {
+ unimplemented!()
+ }
+ fn build_dispatcher_not_found(_args: Vec<String>) -> AnyOutput<MockCollect> {
+ unimplemented!()
+ }
+ fn build_empty_result() -> AnyOutput<MockCollect> {
+ unimplemented!()
+ }
+ fn render(_any: AnyOutput<MockCollect>, _r: &mut RenderResult) {
+ unimplemented!()
+ }
+ fn render_help(_any: AnyOutput<MockCollect>, _r: &mut RenderResult) {
+ unimplemented!()
+ }
+ fn do_chain(_any: AnyOutput<MockCollect>) -> ChainProcess<MockCollect> {
+ unimplemented!()
+ }
+ #[cfg(feature = "comp")]
+ fn do_comp(_any: &AnyOutput<MockCollect>, _ctx: &crate::ShellContext) -> crate::Suggest {
+ unimplemented!()
+ }
+ fn has_renderer(_any: &AnyOutput<MockCollect>) -> bool {
+ unimplemented!()
+ }
+ fn has_chain(_any: &AnyOutput<MockCollect>) -> bool {
+ unimplemented!()
+ }
+
+ #[cfg(feature = "general_renderer")]
+ fn general_render(
+ _any: AnyOutput<MockCollect>,
+ _setting: &crate::GeneralRendererSetting,
+ ) -> Result<RenderResult, crate::error::GeneralRendererSerializeError> {
+ unimplemented!()
+ }
+ }
+
+ #[test]
+ fn test_is_completing_with_comp_subcommand() {
+ let program: Program<MockCollect> =
+ Program::new_with_args(["program", "__comp", "some", "args"]);
+ assert!(program.is_completing());
+ }
+
+ #[test]
+ fn test_is_completing_with_normal_subcommand() {
+ let program: Program<MockCollect> = Program::new_with_args(["program", "normal", "cmd"]);
+ assert!(!program.is_completing());
+ }
+
+ #[test]
+ fn test_is_completing_with_no_args() {
+ let program: Program<MockCollect> = Program::new_with_args(["program"]);
+ assert!(!program.is_completing());
+ }
+}
diff --git a/mingling_core/src/comp/shell_ctx.rs b/mingling_core/src/comp/shell_ctx.rs
index 35758e9..9d84aa7 100644
--- a/mingling_core/src/comp/shell_ctx.rs
+++ b/mingling_core/src/comp/shell_ctx.rs
@@ -237,6 +237,7 @@ impl ShellContext {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::SuggestItem;
#[test]
fn test_try_from_full_args() {
@@ -315,4 +316,128 @@ mod tests {
let context = ShellContext::try_from(args).unwrap();
assert_eq!(context.all_words, vec!["cmd", "arg1", "arg2"]);
}
+
+ #[test]
+ fn test_filling_argument_first_true() {
+ let ctx = ShellContext {
+ previous_word: "--flag".to_string(),
+ current_word: "".to_string(),
+ all_words: vec!["cmd".to_string(), "--flag".to_string()],
+ ..Default::default()
+ };
+ assert!(ctx.filling_argument_first(&["--flag", "-f"][..]));
+ }
+
+ #[test]
+ fn test_filling_argument_first_false() {
+ let ctx = ShellContext {
+ previous_word: "--flag".to_string(),
+ current_word: "".to_string(),
+ all_words: vec![
+ "cmd".to_string(),
+ "--flag".to_string(),
+ "--flag".to_string(),
+ ],
+ ..Default::default()
+ };
+ assert!(!ctx.filling_argument_first(&["--flag", "-f"][..]));
+ }
+
+ #[test]
+ fn test_filling_argument_matches() {
+ let ctx = ShellContext {
+ previous_word: "--flag".to_string(),
+ current_word: "".to_string(),
+ all_words: vec!["cmd".to_string(), "--flag".to_string()],
+ ..Default::default()
+ };
+ assert!(ctx.filling_argument(&["--flag", "-f"][..]));
+ }
+
+ #[test]
+ fn test_filling_argument_no_match() {
+ let ctx = ShellContext {
+ previous_word: "other".to_string(),
+ current_word: "".to_string(),
+ all_words: vec!["cmd".to_string(), "other".to_string()],
+ ..Default::default()
+ };
+ assert!(!ctx.filling_argument(&["--flag", "-f"][..]));
+ }
+
+ #[test]
+ fn test_typing_argument_starts_with_dash() {
+ // On Windows typing_argument checks current_word.is_empty()
+ // On other platforms it checks current_word.starts_with("-")
+ let current_word = if cfg!(target_os = "windows") {
+ "".to_string()
+ } else {
+ "--verbose".to_string()
+ };
+ let ctx = ShellContext {
+ previous_word: "".to_string(),
+ current_word,
+ all_words: vec!["cmd".to_string(), "--verbose".to_string()],
+ ..Default::default()
+ };
+ assert!(ctx.typing_argument());
+ }
+
+ #[test]
+ fn test_typing_argument_no_dash() {
+ let ctx = ShellContext {
+ previous_word: "".to_string(),
+ current_word: "somefile".to_string(),
+ all_words: vec!["cmd".to_string(), "somefile".to_string()],
+ ..Default::default()
+ };
+ assert!(!ctx.typing_argument());
+ }
+
+ #[test]
+ fn test_strip_typed_argument_suggest() {
+ let ctx = ShellContext {
+ all_words: vec!["--flag".to_string()],
+ ..Default::default()
+ };
+ let suggest: Suggest = vec!["--flag", "--other"].into();
+ let stripped = ctx.strip_typed_argument(suggest);
+ match stripped {
+ Suggest::Suggest(set) => {
+ assert_eq!(set.len(), 1);
+ assert!(set.contains(&SuggestItem::new("--other".to_string())));
+ }
+ Suggest::FileCompletion => panic!("expected Suggest variant"),
+ }
+ }
+
+ #[test]
+ fn test_strip_typed_argument_file_completion() {
+ let ctx = ShellContext {
+ all_words: vec!["--flag".to_string()],
+ ..Default::default()
+ };
+ let stripped = ctx.strip_typed_argument(Suggest::FileCompletion);
+ assert_eq!(stripped, Suggest::FileCompletion);
+ }
+
+ #[test]
+ fn test_get_typed_arguments() {
+ let ctx = ShellContext {
+ previous_word: "".to_string(),
+ current_word: "".to_string(),
+ all_words: vec![
+ "cmd".to_string(),
+ "--flag".to_string(),
+ "--other".to_string(),
+ "file.txt".to_string(),
+ ],
+ ..Default::default()
+ };
+ let typed = ctx.get_typed_arguments();
+ let expected: HashSet<String> = vec!["--flag".to_string(), "--other".to_string()]
+ .into_iter()
+ .collect();
+ assert_eq!(typed, expected);
+ }
}
diff --git a/mingling_core/src/comp/suggest.rs b/mingling_core/src/comp/suggest.rs
index cd025a4..03842e1 100644
--- a/mingling_core/src/comp/suggest.rs
+++ b/mingling_core/src/comp/suggest.rs
@@ -183,3 +183,266 @@ impl From<(String, String)> for SuggestItem {
Self::new_with_desc(suggest, description)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_suggest_new_creates_empty() {
+ let s = Suggest::new();
+ match s {
+ Suggest::Suggest(set) => assert!(set.is_empty(), "expected empty BTreeSet"),
+ Suggest::FileCompletion => panic!("expected Suggest variant"),
+ }
+ }
+
+ #[test]
+ fn test_suggest_file_comp() {
+ assert_eq!(Suggest::file_comp(), Suggest::FileCompletion);
+ }
+
+ #[test]
+ fn test_from_vec_string() {
+ let items = vec!["foo".to_string(), "bar".to_string()];
+ let suggest: Suggest = items.into();
+ match suggest {
+ Suggest::Suggest(set) => {
+ assert_eq!(set.len(), 2);
+ assert!(set.contains(&SuggestItem::new("foo".to_string())));
+ assert!(set.contains(&SuggestItem::new("bar".to_string())));
+ }
+ Suggest::FileCompletion => panic!("expected Suggest variant"),
+ }
+ }
+
+ #[test]
+ fn test_from_vec_str_ref() {
+ let items = vec!["a", "b", "c"];
+ let suggest: Suggest = items.into();
+ match suggest {
+ Suggest::Suggest(set) => {
+ assert_eq!(set.len(), 3);
+ }
+ Suggest::FileCompletion => panic!("expected Suggest variant"),
+ }
+ }
+
+ #[test]
+ fn test_from_array_str_ref() {
+ let items = ["x", "y", "z"];
+ let suggest: Suggest = items.into();
+ match suggest {
+ Suggest::Suggest(set) => {
+ assert_eq!(set.len(), 3);
+ }
+ Suggest::FileCompletion => panic!("expected Suggest variant"),
+ }
+ }
+
+ #[test]
+ fn test_deref_suggest() {
+ let s: Suggest = ["hello"].into();
+ let set: &BTreeSet<SuggestItem> = &*s;
+ assert_eq!(set.len(), 1);
+ }
+
+ #[test]
+ #[should_panic(expected = "Cannot deref FileCompletion variant")]
+ fn test_deref_file_completion_panics() {
+ let s = Suggest::FileCompletion;
+ let _ = &*s;
+ }
+
+ #[test]
+ fn test_deref_mut_suggest() {
+ let mut s = Suggest::Suggest(BTreeSet::new());
+ s.insert(SuggestItem::new("inserted".to_string()));
+ assert_eq!(s.len(), 1);
+ }
+
+ #[test]
+ #[should_panic(expected = "Cannot deref_mut FileCompletion variant")]
+ fn test_deref_mut_file_completion_panics() {
+ let mut s = Suggest::FileCompletion;
+ let _ = &mut *s;
+ }
+
+ #[test]
+ fn test_suggest_item_new() {
+ let item = SuggestItem::new("hello".to_string());
+ assert!(matches!(item, SuggestItem::Simple(ref s) if s == "hello"));
+ }
+
+ #[test]
+ fn test_suggest_item_new_with_desc() {
+ let item = SuggestItem::new_with_desc("hello".to_string(), "desc".to_string());
+ assert!(
+ matches!(item, SuggestItem::WithDescription(ref s, ref d) if s == "hello" && d == "desc")
+ );
+ }
+
+ #[test]
+ fn test_with_desc_replaces_existing() {
+ let item = SuggestItem::new_with_desc("foo".to_string(), "old".to_string())
+ .with_desc("new".to_string());
+ assert_eq!(item.description(), Some(&"new".to_string()));
+ }
+
+ #[test]
+ fn test_with_desc_on_simple() {
+ let item = SuggestItem::new("foo".to_string()).with_desc("added".to_string());
+ assert_eq!(item.description(), Some(&"added".to_string()));
+ }
+
+ #[test]
+ fn test_suggest_returns_text() {
+ let simple = SuggestItem::new("simple".to_string());
+ let desc = SuggestItem::new_with_desc("desc".to_string(), "d".to_string());
+ assert_eq!(simple.suggest(), &"simple".to_string());
+ assert_eq!(desc.suggest(), &"desc".to_string());
+ }
+
+ #[test]
+ fn test_description() {
+ let simple = SuggestItem::new("x".to_string());
+ assert_eq!(simple.description(), None);
+
+ let desc = SuggestItem::new_with_desc("x".to_string(), "y".to_string());
+ assert_eq!(desc.description(), Some(&"y".to_string()));
+ }
+
+ #[test]
+ fn test_set_suggest() {
+ let mut item = SuggestItem::new("old".to_string());
+ item.set_suggest("new".to_string());
+ assert_eq!(item.suggest(), &"new".to_string());
+
+ let mut item = SuggestItem::new_with_desc("old".to_string(), "d".to_string());
+ item.set_suggest("newer".to_string());
+ assert_eq!(item.suggest(), &"newer".to_string());
+ }
+
+ #[test]
+ fn test_set_description_on_simple() {
+ let mut item = SuggestItem::new("text".to_string());
+ item.set_description("added".to_string());
+ assert_eq!(item.description(), Some(&"added".to_string()));
+ }
+
+ #[test]
+ fn test_set_description_replaces_existing() {
+ let mut item = SuggestItem::new_with_desc("text".to_string(), "old".to_string());
+ item.set_description("new".to_string());
+ assert_eq!(item.description(), Some(&"new".to_string()));
+ }
+
+ #[test]
+ fn test_remove_desc_on_simple() {
+ let mut item = SuggestItem::new("text".to_string());
+ assert_eq!(item.remove_desc(), None);
+ assert!(matches!(item, SuggestItem::Simple(_)));
+ }
+
+ #[test]
+ fn test_remove_desc_on_with_description() {
+ let mut item = SuggestItem::new_with_desc("text".to_string(), "desc".to_string());
+ let desc = item.remove_desc();
+ assert_eq!(desc, Some("desc".to_string()));
+ assert!(matches!(item, SuggestItem::Simple(ref s) if s == "text"));
+ }
+
+ #[test]
+ fn test_ord_by_suggest_text() {
+ let mut items = vec![
+ SuggestItem::new("z".to_string()),
+ SuggestItem::new("a".to_string()),
+ SuggestItem::new("m".to_string()),
+ ];
+ items.sort();
+ assert_eq!(items[0].suggest(), &"a".to_string());
+ assert_eq!(items[1].suggest(), &"m".to_string());
+ assert_eq!(items[2].suggest(), &"z".to_string());
+ }
+
+ #[test]
+ fn test_ord_with_description() {
+ let mut items = vec![
+ SuggestItem::new_with_desc("z".to_string(), "zzz".to_string()),
+ SuggestItem::new("a".to_string()),
+ SuggestItem::new_with_desc("m".to_string(), "mmm".to_string()),
+ ];
+ items.sort();
+ assert_eq!(items[0].suggest(), &"a".to_string());
+ assert_eq!(items[1].suggest(), &"m".to_string());
+ assert_eq!(items[2].suggest(), &"z".to_string());
+ }
+
+ #[test]
+ fn test_from_string_for_suggest_item() {
+ let item: SuggestItem = "test".to_string().into();
+ assert!(matches!(item, SuggestItem::Simple(ref s) if s == "test"));
+ }
+
+ #[test]
+ fn test_from_tuple_for_suggest_item() {
+ let item: SuggestItem = ("key".to_string(), "val".to_string()).into();
+ assert!(
+ matches!(item, SuggestItem::WithDescription(ref s, ref d) if s == "key" && d == "val")
+ );
+ }
+
+ #[test]
+ fn test_default_suggest_item() {
+ let item = SuggestItem::default();
+ assert!(matches!(item, SuggestItem::Simple(ref s) if s.is_empty()));
+ }
+
+ #[test]
+ fn test_strip_typed_argument_removes_typed() {
+ let ctx = ShellContext {
+ all_words: vec!["--verbose".to_string(), "--help".to_string()],
+ ..ShellContext::default()
+ };
+
+ let suggest: Suggest = vec!["--verbose", "--output", "--help"].into();
+ let stripped = suggest.strip_typed_argument(&ctx);
+
+ match stripped {
+ Suggest::Suggest(set) => {
+ assert_eq!(set.len(), 1);
+ assert!(set.contains(&SuggestItem::new("--output".to_string())));
+ }
+ Suggest::FileCompletion => panic!("expected Suggest variant"),
+ }
+ }
+
+ #[test]
+ fn test_strip_typed_argument_passes_file_completion() {
+ let ctx = ShellContext {
+ all_words: vec!["--verbose".to_string()],
+ ..ShellContext::default()
+ };
+
+ let stripped = Suggest::FileCompletion.strip_typed_argument(&ctx);
+ assert_eq!(stripped, Suggest::FileCompletion);
+ }
+
+ #[test]
+ fn test_strip_typed_argument_keeps_untyped() {
+ let ctx = ShellContext {
+ all_words: vec!["--verbose".to_string()],
+ ..ShellContext::default()
+ };
+
+ let suggest: Suggest = vec!["--output", "--help"].into();
+ let stripped = suggest.strip_typed_argument(&ctx);
+
+ match stripped {
+ Suggest::Suggest(set) => {
+ assert_eq!(set.len(), 2);
+ }
+ Suggest::FileCompletion => panic!("expected Suggest variant"),
+ }
+ }
+}