aboutsummaryrefslogtreecommitdiff
path: root/mingling_picker/test/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-17 03:24:51 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-17 03:25:24 +0800
commitb9f208deed7b8e012fbcd84202e2fb1d5eb8eeb9 (patch)
tree2e0f00651b0cff09a2f6bafd6ce35046532ffa81 /mingling_picker/test/src
parent8b2c209c2f111e0e208de5dde9df6c4bd3ea1052 (diff)
feat(picker2): complete Picker2 prototype
Picker2 replaces the original Picker1 with a two-phase (tag → pick) + mask-bitmap architecture, decoupling argument matching from type conversion via a composable matcher pipeline. Architecture - FlagMatcher — boolean flags (--verbose) - ArgMatcher — single flag+value pairs (--name Alice) - MultiArgMatcher — multi-value flag groups (--files a.txt b.txt) - PositionalMatcher — positional arguments, respects `--` - SingleMatcher — composite for Single-type Pickables Types - Flag (Active/Inactive) — semantic bool flag value - String + 14 numeric types via SinglePickable trait - Vec<T> — greedy multi-value (all SinglePickable types) - VecUntil<T> — bounded multi-value via BoundaryCheck trait - VecUntil<T>, pick_string, pick_numbers, pick_bool, pick_flag Style system - UNIX_STYLE (kebab), POWERSHELL_STYLE (Pascal), WINDOWS_STYLE (Pascal) - naming_case auto-conversion via just_fmt integration - Style-aware separator (= for Unix, : for PS/Windows) Infrastructure - internal_repeat! macro generates PickerPattern1..=32 - SinglePickable blanket impl → Pickable - MultiPickableWithBoundary trait with greedy/bounded variants - 151 integration tests - Docs updated for parser feature
Diffstat (limited to 'mingling_picker/test/src')
-rw-r--r--mingling_picker/test/src/lib.rs23
-rw-r--r--mingling_picker/test/src/test.rs10
-rw-r--r--mingling_picker/test/src/test/arg_matcher_test.rs289
-rw-r--r--mingling_picker/test/src/test/basic_test.rs223
-rw-r--r--mingling_picker/test/src/test/multi_arg_test.rs181
-rw-r--r--mingling_picker/test/src/test/multi_value_test.rs66
-rw-r--r--mingling_picker/test/src/test/pos_matcher_test.rs141
-rw-r--r--mingling_picker/test/src/test/priority_test.rs85
-rw-r--r--mingling_picker/test/src/test/route_test.rs200
-rw-r--r--mingling_picker/test/src/test/style_test.rs300
-rw-r--r--mingling_picker/test/src/test/value_flag_test.rs174
-rw-r--r--mingling_picker/test/src/test/value_string_test.rs171
12 files changed, 1863 insertions, 0 deletions
diff --git a/mingling_picker/test/src/lib.rs b/mingling_picker/test/src/lib.rs
new file mode 100644
index 0000000..eac6cad
--- /dev/null
+++ b/mingling_picker/test/src/lib.rs
@@ -0,0 +1,23 @@
+// Using `assert_eq!(x, true)` is clearer than `assert!(x)` for expressing expected values
+//
+// BECAUSE `assert!` only checks if the boolean value is true,
+// while `assert_eq!` explicitly shows the expected value
+#![allow(clippy::bool_assert_comparison)]
+
+#[cfg(test)]
+mod test;
+
+use mingling_picker::parselib::MaskedArg;
+
+/// Create a single `MaskedArg` from a raw string and its original index.
+pub fn make_masked(raw: &str, idx: usize) -> MaskedArg<'_> {
+ MaskedArg { raw, raw_idx: idx }
+}
+
+/// Create a `Vec<MaskedArg>` from an array of `(raw, raw_idx)` pairs.
+pub fn make_args<'a>(pairs: &'a [(&'a str, usize)]) -> Vec<MaskedArg<'a>> {
+ pairs
+ .iter()
+ .map(|&(raw, idx)| MaskedArg { raw, raw_idx: idx })
+ .collect()
+}
diff --git a/mingling_picker/test/src/test.rs b/mingling_picker/test/src/test.rs
new file mode 100644
index 0000000..9c53514
--- /dev/null
+++ b/mingling_picker/test/src/test.rs
@@ -0,0 +1,10 @@
+mod arg_matcher_test;
+mod basic_test;
+mod multi_arg_test;
+mod multi_value_test;
+mod pos_matcher_test;
+mod priority_test;
+mod route_test;
+mod style_test;
+mod value_flag_test;
+mod value_string_test;
diff --git a/mingling_picker/test/src/test/arg_matcher_test.rs b/mingling_picker/test/src/test/arg_matcher_test.rs
new file mode 100644
index 0000000..7b37bc8
--- /dev/null
+++ b/mingling_picker/test/src/test/arg_matcher_test.rs
@@ -0,0 +1,289 @@
+use mingling_picker::PickerArgInfo;
+use mingling_picker::parselib::{ArgMatcher, Matcher, POWERSHELL_STYLE, UNIX_STYLE};
+
+use crate::make_args;
+
+// on_match_one — Named
+
+#[test]
+fn test_match_one_named_basic() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--name", 0), ("Alice", 1)]);
+ let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_match_one_named_eq_mode() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--name=Alice", 0)]);
+ let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_match_one_named_no_match() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--other", 0), ("Alice", 1)]);
+ let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+#[test]
+fn test_match_one_named_short_flag() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ info.set_short('n');
+ let args = make_args(&[("-n", 0), ("Alice", 1)]);
+ let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_match_one_named_after_end_of_options() {
+ // Flags after `--` should not be matched.
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--", 0), ("--name", 1), ("Alice", 2)]);
+ let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+// on_match_one — Positional
+
+#[test]
+fn test_match_one_positional_basic() {
+ let mut info = PickerArgInfo::new();
+ info.set_positional(true);
+ let args = make_args(&[("file.txt", 0)]);
+ let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_match_one_positional_takes_first() {
+ let mut info = PickerArgInfo::new();
+ info.set_positional(true);
+ let args = make_args(&[("a.txt", 0), ("b.txt", 1)]);
+ let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+// on_match_all — Named, single occurrence
+
+#[test]
+fn test_match_all_named_flag_plus_value() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--name", 0), ("Alice", 1)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1]);
+}
+
+#[test]
+fn test_match_all_named_eq_mode() {
+ // --name=Alice: value is inline, only tag the flag position.
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--name=Alice", 0)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0]);
+}
+
+#[test]
+fn test_match_all_named_no_value() {
+ // Flag at end with no following arg: only tag the flag.
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--name", 0)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0]);
+}
+
+#[test]
+fn test_match_all_named_value_looks_like_flag() {
+ // The next arg looks like a flag — still tag it.
+ // Validation is the Pickable's responsibility.
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--name", 0), ("--other", 1)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1]);
+}
+
+// on_match_all — Named, multiple occurrences (Single per flag)
+
+#[test]
+fn test_match_all_named_two_occurrences() {
+ // --name Alice --name Bob → each occurrence gets one value.
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--name", 0), ("Alice", 1), ("--name", 2), ("Bob", 3)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1, 2, 3]);
+}
+
+#[test]
+fn test_match_all_named_skips_non_matching_args() {
+ // --val a b --val d → only pairs (0,1) and (3,4); idx 2 ("b") left free.
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("--val", 3), ("d", 4)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1, 3, 4]);
+}
+
+// on_match_all — Named, short flag
+
+#[test]
+fn test_match_all_named_short_flag() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ info.set_short('n');
+ let args = make_args(&[("-n", 0), ("Alice", 1)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1]);
+}
+
+// on_match_all — Named, eq + non-eq mixed
+
+#[test]
+fn test_match_all_named_mixed_eq_and_regular() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--name=Alice", 0), ("--name", 1), ("Bob", 2)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1, 2]);
+}
+
+// on_match_all — Named, case insensitive (PowerShell)
+
+#[test]
+fn test_match_all_named_powershell_case_insensitive() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("Name");
+ let args = make_args(&[("-name", 0), ("Alice", 1)]);
+ let result = ArgMatcher::on_match_all(&args, &POWERSHELL_STYLE, &info);
+ assert_eq!(result, vec![0, 1]);
+}
+
+// on_match_all — Positional
+
+#[test]
+fn test_match_all_positional_single() {
+ let mut info = PickerArgInfo::new();
+ info.set_positional(true);
+ let args = make_args(&[("file.txt", 0)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0]);
+}
+
+#[test]
+fn test_match_all_positional_multiple() {
+ let mut info = PickerArgInfo::new();
+ info.set_positional(true);
+ let args = make_args(&[("a.txt", 0), ("b.txt", 1)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1]);
+}
+
+// End-of-options marker (`--`)
+
+#[test]
+fn test_match_all_named_stops_at_end_of_options() {
+ // --name before `--` should match, --name after should not.
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[
+ ("--name", 0),
+ ("Alice", 1),
+ ("--", 2),
+ ("--name", 3),
+ ("Bob", 4),
+ ]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1]);
+}
+
+#[test]
+fn test_match_all_positional_stops_at_end_of_options() {
+ let mut info = PickerArgInfo::new();
+ info.set_positional(true);
+ let args = make_args(&[("before", 0), ("--", 1), ("after", 2)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0]);
+}
+
+// Empty args
+
+#[test]
+fn test_match_one_empty() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = vec![];
+ let result = ArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+#[test]
+fn test_match_all_empty() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = vec![];
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert!(result.is_empty());
+}
+
+// Verify that -- itself is never matched as a flag
+
+#[test]
+fn test_match_all_end_of_options_not_matched() {
+ // `--` should neither match as a flag nor take a value.
+ let mut info = PickerArgInfo::new();
+ info.set_long("name");
+ let args = make_args(&[("--", 0)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert!(result.is_empty());
+}
+
+// `--` should never be consumed as a value by a named flag.
+
+#[test]
+fn test_match_all_named_does_not_consume_end_marker() {
+ // `--flag` before `--`, but the next position IS `--`.
+ // Only tag the flag, don't consume the end-of-options marker.
+ let mut info = PickerArgInfo::new();
+ info.set_long("flag");
+ let args = make_args(&[("--flag", 0), ("--", 1), ("value", 2)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0], "should NOT consume -- as a value");
+}
+
+#[test]
+fn test_match_all_named_does_not_consume_end_marker_flag_before_end() {
+ // Simulates: --flag -- you where -- is end-of-options.
+ // The flag should be tagged but -- should NOT be consumed as its value.
+ let mut info = PickerArgInfo::new();
+ info.set_long("flag");
+
+ let args = make_args(&[("--flag", 0), ("--", 1), ("you", 2)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(
+ result,
+ vec![0],
+ "flag before --: only tag flag, leave -- for positional"
+ );
+}
+
+#[test]
+fn test_match_all_flag_after_end_has_value() {
+ // Flag after `--` should not match at all.
+ let mut info = PickerArgInfo::new();
+ info.set_long("flag");
+ let args = make_args(&[("--", 0), ("--flag", 1), ("value", 2)]);
+ let result = ArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert!(result.is_empty());
+}
diff --git a/mingling_picker/test/src/test/basic_test.rs b/mingling_picker/test/src/test/basic_test.rs
new file mode 100644
index 0000000..78b154e
--- /dev/null
+++ b/mingling_picker/test/src/test/basic_test.rs
@@ -0,0 +1,223 @@
+use mingling_picker::{IntoPicker, macros::arg};
+
+// Basic bool flag — present / absent
+
+#[test]
+fn test_bool_flag_present() {
+ let args = vec!["--verbose"];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![verbose: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, true);
+}
+
+#[test]
+fn test_bool_flag_absent() {
+ let args: Vec<&str> = vec![];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![verbose: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, false);
+}
+
+// Short flag — '-v'
+
+#[test]
+fn test_bool_short_flag_present() {
+ let args = vec!["-v"];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![verbose: bool, 'v'])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, true);
+}
+
+// Multiple bool flags at once
+
+#[test]
+fn test_two_bool_flags_both_present() {
+ // UNIX_STYLE now uses Kebab naming: `flag_a` → "flag-a" → --flag-a
+ let args = vec!["--flag-a", "--flag-b"];
+ let (a, b) = args
+ .to_picker()
+ .pick(&arg![flag_a: bool])
+ .or_default()
+ .pick(&arg![flag_b: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(a, true);
+ assert_eq!(b, true);
+}
+
+#[test]
+fn test_two_bool_flags_one_present() {
+ let args = vec!["--flag-a"];
+ let (a, b) = args
+ .to_picker()
+ .pick(&arg![flag_a: bool])
+ .or_default()
+ .pick(&arg![flag_b: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(a, true);
+ assert_eq!(b, false);
+}
+
+#[test]
+fn test_two_bool_flags_neither_present() {
+ let args: Vec<&str> = vec![];
+ let (a, b) = args
+ .to_picker()
+ .pick(&arg![flag_a: bool])
+ .or_default()
+ .pick(&arg![flag_b: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(a, false);
+ assert_eq!(b, false);
+}
+
+// Mixed short and long flags
+
+#[test]
+fn test_short_and_long_flags() {
+ let args = vec!["-a", "--long-b"];
+ let (a, b) = args
+ .to_picker()
+ .pick(&arg![flag_a: bool, 'a'])
+ .or_default()
+ .pick(&arg![long_b: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(a, true);
+ assert_eq!(b, true);
+}
+
+// Flags after `--` (end-of-options marker) should not be parsed.
+
+#[test]
+fn test_flag_after_end_of_options() {
+ let args = vec!["--", "--verbose"];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![verbose: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, false);
+}
+
+// Alias matching for bool flags
+
+#[test]
+fn test_bool_flag_with_alias() {
+ let args = vec!["--cfg"];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![config: bool, "cfg"])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, true);
+}
+
+#[test]
+fn test_bool_flag_primary_name() {
+ let args = vec!["--config"];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![config: bool, "cfg"])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, true);
+}
+
+// Short flag + alias for bool flag
+
+#[test]
+fn test_bool_flag_short_and_alias() {
+ let args = vec!["-v"];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![verbose: bool, 'v', "cfg"])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, true);
+}
+
+// Default values: .or() / .or_default()
+
+#[test]
+fn test_or_default_without_args() {
+ let args: Vec<&str> = vec![];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![verbose: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, false);
+}
+
+#[test]
+fn test_or_custom_default() {
+ let args: Vec<&str> = vec![];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![verbose: bool])
+ .or(|| true)
+ .unwrap();
+ assert_eq!(parsed, true);
+}
+
+// to_result / to_option interface
+
+#[test]
+fn test_to_result_ok() {
+ let args = vec!["--verbose"];
+ let result = args
+ .to_picker()
+ .pick(&arg![verbose: bool])
+ .or_default()
+ .to_result();
+ assert_eq!(result, Ok(true));
+}
+
+#[test]
+fn test_to_option_some() {
+ let args = vec!["--verbose"];
+ let opt = args
+ .to_picker()
+ .pick(&arg![verbose: bool])
+ .or_default()
+ .to_option();
+ assert_eq!(opt, Some(true));
+}
+
+// Chain with_route passthrough
+
+#[test]
+fn test_with_route_chain() {
+ let args = vec!["--flag"];
+ let parsed = args
+ .with_route::<String>()
+ .pick(&arg![flag: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, true);
+}
+
+// Unrelated flag should not match
+
+#[test]
+fn test_unrelated_flag_does_not_match() {
+ let args = vec!["--other"];
+ let parsed = args
+ .to_picker()
+ .pick(&arg![verbose: bool])
+ .or_default()
+ .unwrap();
+ assert_eq!(parsed, false);
+}
diff --git a/mingling_picker/test/src/test/multi_arg_test.rs b/mingling_picker/test/src/test/multi_arg_test.rs
new file mode 100644
index 0000000..377357e
--- /dev/null
+++ b/mingling_picker/test/src/test/multi_arg_test.rs
@@ -0,0 +1,181 @@
+use mingling_picker::parselib::{Matcher, MultiArgMatcher, UNIX_STYLE};
+use mingling_picker::PickerArgInfo;
+
+use crate::make_args;
+
+// on_match_one — basic
+
+#[test]
+fn test_multi_one_finds_first_flag() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--other", 0), ("--val", 1), ("a", 2), ("b", 3)]);
+ let result = MultiArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(1));
+}
+
+#[test]
+fn test_multi_one_no_match() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--other", 0)]);
+ let result = MultiArgMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+// on_match_all — named, basic multi-value
+
+#[test]
+fn test_multi_all_flag_takes_all_values_until_next_flag() {
+ // --val a b c → all three values belong to --val
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("c", 3)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1, 2, 3]);
+}
+
+#[test]
+fn test_multi_all_stops_at_next_flag() {
+ // --val a b --other c d → only a,b belong to --val
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--val", 0), ("a", 1), ("b", 2), ("--other", 3), ("c", 4)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1, 2]);
+}
+
+#[test]
+fn test_multi_all_flag_no_values() {
+ // --val at end with no values → just the flag
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--val", 0)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0]);
+}
+
+#[test]
+fn test_multi_all_empty() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = vec![];
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert!(result.is_empty());
+}
+
+// on_match_all — named, multiple occurrences of same flag
+
+#[test]
+fn test_multi_all_two_occurrences() {
+ // --val a b --val c d → two groups
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[
+ ("--val", 0), ("a", 1), ("b", 2),
+ ("--val", 3), ("c", 4), ("d", 5),
+ ]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1, 2, 3, 4, 5]);
+}
+
+#[test]
+fn test_multi_all_skips_non_matching_args() {
+ // --val a --other b --val c → only --val groups
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[
+ ("--val", 0), ("a", 1),
+ ("--other", 2), ("b", 3),
+ ("--val", 4), ("c", 5),
+ ]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1, 4, 5]);
+}
+
+// on_match_all — named, eq mode
+
+#[test]
+fn test_multi_all_eq_mode() {
+ // --val=a b → eq mode + one extra value
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--val=a", 0), ("b", 1)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1]);
+}
+
+#[test]
+fn test_multi_all_eq_mode_no_extra() {
+ // --val=a alone → just the eq flag
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--val=a", 0)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0]);
+}
+
+#[test]
+fn test_multi_all_mixed_eq_and_regular() {
+ // --val=a b --val c d
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[
+ ("--val=a", 0), ("b", 1), ("--val", 2), ("c", 3), ("d", 4),
+ ]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1, 2, 3, 4]);
+}
+
+// on_match_all — short flag
+
+#[test]
+fn test_multi_all_short_flag() {
+ let mut info = PickerArgInfo::new();
+ info.set_short('v');
+ info.set_long("val");
+ let args = make_args(&[("-v", 0), ("a", 1), ("b", 2)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1, 2]);
+}
+
+// on_match_all — end-of-options marker
+
+#[test]
+fn test_multi_all_stops_at_end_of_options() {
+ // --val a -- b → stops before `--`
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--val", 0), ("a", 1), ("--", 2), ("b", 3)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1]);
+}
+
+#[test]
+fn test_multi_all_flag_after_end_ignored() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("val");
+ let args = make_args(&[("--", 0), ("--val", 1), ("a", 2)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert!(result.is_empty());
+}
+
+// on_match_all — positional
+
+#[test]
+fn test_multi_all_positional() {
+ let mut info = PickerArgInfo::new();
+ info.set_positional(true);
+ let args = make_args(&[("a.txt", 0), ("b.txt", 1)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0, 1]);
+}
+
+#[test]
+fn test_multi_all_positional_stops_at_end_of_options() {
+ let mut info = PickerArgInfo::new();
+ info.set_positional(true);
+ let args = make_args(&[("a.txt", 0), ("--", 1), ("b.txt", 2)]);
+ let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![0]);
+}
diff --git a/mingling_picker/test/src/test/multi_value_test.rs b/mingling_picker/test/src/test/multi_value_test.rs
new file mode 100644
index 0000000..0f56517
--- /dev/null
+++ b/mingling_picker/test/src/test/multi_value_test.rs
@@ -0,0 +1,66 @@
+use mingling_picker::value::{Flag, VecUntil};
+use mingling_picker::{IntoPicker, macros::arg};
+
+#[test]
+fn test_vec_until_i16_named() {
+ let (nums, rest): (VecUntil<i16>, Flag) = vec!["--nums", "1", "2", "3", "abc"]
+ .to_picker()
+ .pick(&arg![nums: VecUntil<i16>])
+ .pick(&arg![rest: Flag])
+ .unwrap();
+ assert_eq!(*nums, vec![1i16, 2, 3]);
+ assert_eq!(rest, Flag::Inactive);
+}
+
+#[test]
+fn test_vec_until_i16_stops_at_non_number() {
+ let nums: VecUntil<i16> = vec!["--nums", "42", "abc", "100"]
+ .to_picker()
+ .pick(&arg![nums: VecUntil<i16>])
+ .unwrap();
+ assert_eq!(*nums, vec![42i16]);
+}
+
+#[test]
+fn test_vec_until_i16_empty() {
+ let nums: VecUntil<i16> = vec!["--nums"]
+ .to_picker()
+ .pick(&arg![nums: VecUntil<i16>])
+ .unwrap();
+ assert!(nums.is_empty());
+}
+
+#[test]
+fn test_vec_until_i16_stops_at_next_flag() {
+ let nums: VecUntil<i16> = vec!["--nums", "1", "2", "--other", "3"]
+ .to_picker()
+ .pick(&arg![nums: VecUntil<i16>])
+ .unwrap();
+ assert_eq!(*nums, vec![1i16, 2]);
+}
+
+#[test]
+fn test_vec_until_i16_stops_at_end_of_options() {
+ let nums: VecUntil<i16> = vec!["--nums", "1", "2", "--", "3"]
+ .to_picker()
+ .pick(&arg![nums: VecUntil<i16>])
+ .unwrap();
+ assert_eq!(*nums, vec![1i16, 2]);
+}
+
+// Two VecUntil<T> with different boundary behaviours
+
+#[test]
+fn test_vec_until_f64_and_i16_positional() {
+ // Both are positional VecUntil. f64 takes all valid floats,
+ // i16 takes what's left. But note: "1", "2", "3" are also valid
+ // f64 values, so f64's check_boundary never fires — it consumes
+ // everything, and i16 gets nothing.
+ let (floats, ints): (VecUntil<f64>, VecUntil<i16>) = vec!["1.5", "2.5", "3.5", "1", "2", "3"]
+ .to_picker()
+ .pick(&arg![VecUntil<f64>])
+ .pick(&arg![VecUntil<i16>])
+ .unwrap();
+ assert_eq!(*floats, vec![1.5, 2.5, 3.5]);
+ assert_eq!(*ints, vec![1i16, 2, 3]);
+}
diff --git a/mingling_picker/test/src/test/pos_matcher_test.rs b/mingling_picker/test/src/test/pos_matcher_test.rs
new file mode 100644
index 0000000..b5319f5
--- /dev/null
+++ b/mingling_picker/test/src/test/pos_matcher_test.rs
@@ -0,0 +1,141 @@
+use mingling_picker::PickerArgInfo;
+use mingling_picker::parselib::{MaskedArg, Matcher, PositionalMatcher, UNIX_STYLE, WINDOWS_STYLE};
+
+fn make_args<'a>(pairs: &'a [(&'a str, usize)]) -> Vec<MaskedArg<'a>> {
+ pairs
+ .iter()
+ .map(|&(raw, idx)| MaskedArg { raw, raw_idx: idx })
+ .collect()
+}
+
+// on_match_one — basic
+
+#[test]
+fn test_pos_one_takes_first_non_flag() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--verbose", 0), ("file.txt", 1)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(1));
+}
+
+#[test]
+fn test_pos_one_takes_first_if_no_flag() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("file.txt", 0)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_pos_one_all_flags_returns_none() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--verbose", 0), ("--name", 1)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+#[test]
+fn test_pos_one_empty_returns_none() {
+ let info = PickerArgInfo::new();
+ let args = vec![];
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+// on_match_one — after `--`
+
+#[test]
+fn test_pos_one_after_end_takes_even_flag_like() {
+ // After `--`, accept everything including `--verbose`.
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--", 0), ("--verbose", 1), ("file.txt", 2)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, Some(1));
+}
+
+#[test]
+fn test_pos_one_only_end_returns_none() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--", 0)]);
+ let result = PositionalMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+// on_match_one — Windows style prefix
+
+#[test]
+fn test_pos_one_windows_skips_slash_prefix() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("/Verbose", 0), ("file.txt", 1)]);
+ let result = PositionalMatcher::on_match_one(&args, &WINDOWS_STYLE, &info);
+ assert_eq!(result, Some(1));
+}
+
+// on_match_all — basic
+
+#[test]
+fn test_pos_all_collects_non_flags() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--verbose", 0), ("a.txt", 1), ("--name", 2), ("b.txt", 3)]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![1, 3]);
+}
+
+#[test]
+fn test_pos_all_only_flags_returns_empty() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--a", 0), ("--b", 1)]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert!(result.is_empty());
+}
+
+#[test]
+fn test_pos_all_empty() {
+ let info = PickerArgInfo::new();
+ let args = vec![];
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert!(result.is_empty());
+}
+
+// on_match_all — after `--`
+
+#[test]
+fn test_pos_all_after_end_accepts_everything() {
+ // After `--`, even `--verbose` is accepted as positional.
+ let info = PickerArgInfo::new();
+ let args = make_args(&[
+ ("--verbose", 0),
+ ("a.txt", 1),
+ ("--", 2),
+ ("--flag", 3),
+ ("b.txt", 4),
+ ]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![1, 3, 4]);
+}
+
+#[test]
+fn test_pos_all_only_after_end() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[("--", 0), ("arg1", 1), ("--arg2", 2)]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, vec![1, 2]);
+}
+
+// on_match_all — mixed before/after `--`
+
+#[test]
+fn test_pos_all_mixed() {
+ let info = PickerArgInfo::new();
+ let args = make_args(&[
+ ("infile", 0),
+ ("--verbose", 1),
+ ("--", 2),
+ ("outfile", 3),
+ ("--extra", 4),
+ ]);
+ let result = PositionalMatcher::on_match_all(&args, &UNIX_STYLE, &info);
+ // Before `--`: "infile" (skip --verbose).
+ // After `--`: everything — "outfile", "--extra".
+ assert_eq!(result, vec![0, 3, 4]);
+}
diff --git a/mingling_picker/test/src/test/priority_test.rs b/mingling_picker/test/src/test/priority_test.rs
new file mode 100644
index 0000000..8179389
--- /dev/null
+++ b/mingling_picker/test/src/test/priority_test.rs
@@ -0,0 +1,85 @@
+use mingling_picker::value::Flag;
+use mingling_picker::{IntoPicker, macros::arg};
+
+// Same flag name, different Pickable types
+//
+// PickerArgAttr priority: Flag < Single
+// So Single and Multi should parse BEFORE Flag when sharing
+// the same flag name.
+
+#[test]
+fn test_single_takes_flag_when_sharing_name() {
+ // --name Alice: String consumes it, Flag sees nothing.
+ let (name, verbose): (String, Flag) = vec!["--name", "Alice"]
+ .to_picker()
+ .pick(&arg![name: String]) // Single, parsed first
+ .pick(&arg![name: Flag]) // Flag, parsed second, nothing left
+ .unwrap();
+ assert_eq!(name, "Alice");
+ assert_eq!(verbose, Flag::Inactive);
+}
+
+#[test]
+fn test_flag_only_triggers_when_single_missing() {
+ // --verbose: only Flag matches, String gets default.
+ let (name, verbose): (String, Flag) = vec!["--verbose"]
+ .to_picker()
+ .pick(&arg![name: String]) // Single, no match → default ""
+ .or_default()
+ .pick(&arg![name: Flag]) // Flag, no --name in args → Inactive
+ .unwrap();
+ assert_eq!(name, "");
+ assert_eq!(verbose, Flag::Inactive);
+}
+
+#[test]
+fn test_flag_gets_leftovers_after_single_consumes_value() {
+ // --name Alice --name: String takes "--name Alice", Flag takes "--name".
+ let (name, verbose): (String, Flag) = vec!["--name", "Alice", "--name"]
+ .to_picker()
+ .pick(&arg![name: String]) // Single: tag [0, 1], consumes --name Alice
+ .pick(&arg![name: Flag]) // Flag: tags position 2 (--name)
+ .unwrap();
+ assert_eq!(name, "Alice");
+ assert_eq!(
+ verbose,
+ Flag::Active,
+ "Flag should see --name at position 2 after String consumed positions 0-1"
+ );
+}
+
+#[test]
+fn test_short_flag_sharing_same_letter() {
+ // -n Alice: String takes it, Flag misses.
+ let (name, verbose): (String, Flag) = vec!["-n", "Alice"]
+ .to_picker()
+ .pick(&arg![name: String, 'n']) // Single, parsed first
+ .pick(&arg![name: Flag, 'n']) // Flag, parsed second
+ .unwrap();
+ assert_eq!(name, "Alice");
+ assert_eq!(verbose, Flag::Inactive);
+}
+
+#[test]
+fn test_flag_captures_remaining_after_single_partial_consume() {
+ // --name Alice --verbose: String takes --name Alice, Flag takes --verbose.
+ let (name, verbose): (String, Flag) = vec!["--name", "Alice", "--verbose"]
+ .to_picker()
+ .pick(&arg![name: String]) // Single: tag [0, 1]
+ .pick(&arg![verbose: Flag]) // Flag: tag [2]
+ .unwrap();
+ assert_eq!(name, "Alice");
+ assert_eq!(verbose, Flag::Active);
+}
+
+#[test]
+fn test_single_skips_already_claimed_positions() {
+ // --verbose --name Alice: Flag takes --verbose, String takes --name Alice.
+ let (verbose, name): (Flag, String) = vec!["--verbose", "--name", "Alice"]
+ .to_picker()
+ .pick(&arg![verbose: Flag]) // Flag: tag [0]
+ .pick(&arg![name: String]) // Single: tag [1, 2]
+ .unwrap();
+ assert_eq!(verbose, Flag::Active);
+ assert_eq!(name, "Alice");
+}
diff --git a/mingling_picker/test/src/test/route_test.rs b/mingling_picker/test/src/test/route_test.rs
new file mode 100644
index 0000000..9261db1
--- /dev/null
+++ b/mingling_picker/test/src/test/route_test.rs
@@ -0,0 +1,200 @@
+use mingling_picker::{IntoPicker, macros::arg};
+
+// Route mechanism — or_route
+
+#[test]
+fn test_or_route_triggered_to_result() {
+ // flag not present and no default value → route triggered → Err
+ let args: Vec<&str> = vec![];
+ let result: Result<bool, &'static str> = args
+ .with_route::<&'static str>()
+ .pick(&arg![verbose: bool])
+ .or_route(|| "missing_verbose")
+ .to_result();
+ assert_eq!(result, Err("missing_verbose"));
+}
+
+#[test]
+fn test_or_route_not_triggered_when_flag_present() {
+ // flag present → route not triggered → Ok
+ let args = vec!["--verbose"];
+ let result: Result<bool, &'static str> = args
+ .with_route::<&'static str>()
+ .pick(&arg![verbose: bool])
+ .or_route(|| "missing_verbose")
+ .to_result();
+ assert_eq!(result, Ok(true));
+}
+
+#[test]
+fn test_or_default_priority_over_or_route() {
+ // or_default takes priority over or_route: even if the flag is absent,
+ // having a default prevents the route from being triggered
+ let args: Vec<&str> = vec![];
+ let result: Result<bool, &'static str> = args
+ .with_route::<&'static str>()
+ .pick(&arg![verbose: bool])
+ .or_default()
+ .or_route(|| "should_not_reach")
+ .to_result();
+ assert_eq!(result, Ok(false));
+}
+
+#[test]
+fn test_or_route_to_option_returns_none() {
+ // When route is triggered, to_option returns None
+ let args: Vec<&str> = vec![];
+ let opt: Option<bool> = args
+ .with_route::<&'static str>()
+ .pick(&arg![verbose: bool])
+ .or_route(|| "missing")
+ .to_option();
+ assert_eq!(opt, None);
+}
+
+#[test]
+#[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
+fn test_or_route_unwrap_panics() {
+ let args: Vec<&str> = vec![];
+ args.with_route::<&'static str>()
+ .pick(&arg![verbose: bool])
+ .or_route(|| "missing")
+ .unwrap();
+}
+
+#[test]
+fn test_or_route_with_string_route() {
+ // Route type is String
+ let args: Vec<&str> = vec![];
+ let result: Result<bool, String> = args
+ .with_route::<String>()
+ .pick(&arg![verbose: bool])
+ .or_route(|| "route_hit".to_string())
+ .to_result();
+ assert_eq!(result, Err("route_hit".to_string()));
+}
+
+#[test]
+fn test_or_route_with_custom_enum() {
+ // Route type is a custom enum
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[allow(dead_code)]
+ enum Redirect {
+ Help,
+ Version,
+ }
+
+ let args: Vec<&str> = vec![];
+ let result: Result<bool, Redirect> = args
+ .with_route::<Redirect>()
+ .pick(&arg![verbose: bool])
+ .or_route(|| Redirect::Help)
+ .to_result();
+ assert_eq!(result, Err(Redirect::Help));
+}
+
+// Route mechanism — multiple flags
+
+#[test]
+fn test_two_flags_first_missing_triggers_route() {
+ // Both flags missing; first has or_route → Err("first");
+ // first route wins (first-checked, first-triggered)
+ let args: Vec<&str> = vec![];
+ let result: Result<(bool, bool), &'static str> = args
+ .with_route::<&'static str>()
+ .pick(&arg![flag_a: bool])
+ .or_route(|| "first_missing")
+ .pick(&arg![flag_b: bool])
+ .or_route(|| "second_missing")
+ .to_result();
+ assert_eq!(result, Err("first_missing"));
+}
+
+#[test]
+fn test_two_flags_second_missing_triggers_route() {
+ // First has or_default (no route triggered), second missing with or_route → Err("second")
+ let args: Vec<&str> = vec![];
+ let result: Result<(bool, bool), &'static str> = args
+ .with_route::<&'static str>()
+ .pick(&arg![flag_a: bool])
+ .or_default()
+ .pick(&arg![flag_b: bool])
+ .or_route(|| "second_missing")
+ .to_result();
+ assert_eq!(result, Err("second_missing"));
+}
+
+#[test]
+fn test_two_flags_both_present_route_not_triggered() {
+ // Both flags present → route not triggered → Ok((true, true))
+ let args = vec!["--flag-a", "--flag-b"];
+ let result: Result<(bool, bool), &'static str> = args
+ .with_route::<&'static str>()
+ .pick(&arg![flag_a: bool])
+ .or_route(|| "first_missing")
+ .pick(&arg![flag_b: bool])
+ .or_route(|| "second_missing")
+ .to_result();
+ assert_eq!(result, Ok((true, true)));
+}
+
+#[test]
+fn test_two_flags_first_missing_no_route_second_has_route() {
+ // First missing but has no or_route, second missing with or_route → Err("second")
+ // Note: the first has neither route nor default, so pick failure does not modify error_route
+ let args: Vec<&str> = vec![];
+ let result: Result<(bool, bool), &'static str> = args
+ .with_route::<&'static str>()
+ .pick(&arg![flag_a: bool])
+ // No or_default, no or_route either
+ .pick(&arg![flag_b: bool])
+ .or_route(|| "second_missing")
+ .to_result();
+ assert_eq!(result, Err("second_missing"));
+}
+
+#[test]
+fn test_two_flags_only_second_has_default() {
+ // First is missing with no default/route (v1=NotFound), second is missing but has or_default
+ // Use unpack to check both results
+ let args: Vec<&str> = vec![];
+ let (a, b) = args
+ .to_picker()
+ .pick(&arg![flag_a: bool])
+ .pick(&arg![flag_b: bool])
+ .or_default()
+ .unpack();
+ assert_eq!(a, None);
+ assert_eq!(b, Some(false));
+}
+
+// Route with with_route
+
+#[test]
+fn test_with_route_and_or_route() {
+ // with_route::&lt;String&gt;() sets the Route type + or_route triggers
+ let args: Vec<&str> = vec![];
+ let result: Result<bool, String> = args
+ .with_route::<String>()
+ .pick(&arg![verbose: bool])
+ .or_route(|| "redirected".to_string())
+ .to_result();
+ assert_eq!(result, Err("redirected".to_string()));
+}
+
+#[test]
+fn test_with_route_type_switch() {
+ // with_route switching Route type clears old route_$ and error_route
+ let args: Vec<&str> = vec![];
+ let result: Result<(bool, bool), i32> = args
+ .with_route::<String>()
+ .pick(&arg![verbose: bool])
+ .or_route(|| "string_route".to_string())
+ .with_route::<i32>()
+ .pick(&arg![other: bool])
+ .or_route(|| -1)
+ .to_result();
+ // The first flag is missing, but its or_route is cleared when with_route switches (route_$ = None)
+ // The second flag is missing and has or_route → Err(-1)
+ assert_eq!(result, Err(-1));
+}
diff --git a/mingling_picker/test/src/test/style_test.rs b/mingling_picker/test/src/test/style_test.rs
new file mode 100644
index 0000000..3c337b9
--- /dev/null
+++ b/mingling_picker/test/src/test/style_test.rs
@@ -0,0 +1,300 @@
+use mingling_picker::PickerArgInfo;
+use mingling_picker::parselib::{
+ FlagMatcher, Matcher, POWERSHELL_STYLE, ParserStyle, ParserStyleNamingCase, UNIX_STYLE,
+ WINDOWS_STYLE, build_possible_flags,
+};
+
+use crate::make_masked;
+
+// Style: formatting utilities
+
+#[test]
+fn test_unix_style_flag_string() {
+ assert_eq!(UNIX_STYLE.flag_string('v'), "-v");
+ assert_eq!(UNIX_STYLE.flag_string("verbose"), "--verbose");
+}
+
+#[test]
+fn test_windows_style_flag_string() {
+ assert_eq!(WINDOWS_STYLE.flag_string('v'), "/v");
+ assert_eq!(WINDOWS_STYLE.flag_string("verbose"), "/verbose");
+}
+
+#[test]
+fn test_powershell_style_flag_string() {
+ assert_eq!(POWERSHELL_STYLE.flag_string('v'), "-v");
+ assert_eq!(POWERSHELL_STYLE.flag_string("Verbose"), "-Verbose");
+}
+
+#[test]
+fn test_build_possible_flags_windows() {
+ // Build PickerArgInfo from a flag definition: `verbose: bool`
+ let mut info = PickerArgInfo::new();
+ info.set_long("verbose");
+ let flags = build_possible_flags(&WINDOWS_STYLE, &info);
+ assert_eq!(flags, vec!["/Verbose"]);
+}
+
+#[test]
+fn test_build_possible_flags_with_short_and_alias() {
+ let mut info = PickerArgInfo::new();
+ info.set_short('n');
+ info.set_long("name");
+ info.set_alias(vec!["nickname"]);
+ let flags = build_possible_flags(&UNIX_STYLE, &info);
+ assert_eq!(flags, vec!["-n", "--name", "--nickname"]);
+}
+
+// Style: matching with different styles via Matcher trait
+
+#[test]
+fn test_windows_style_match() {
+ // Windows style: /verbose (case insensitive)
+ let mut info = PickerArgInfo::new();
+ info.set_long("verbose");
+
+ let args = vec![make_masked("/verbose", 0)];
+ let result = FlagMatcher::on_match_one(&args, &WINDOWS_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_windows_style_match_case_insensitive() {
+ // Windows style is case-insensitive: /VERBOSE should match "verbose"
+ let mut info = PickerArgInfo::new();
+ info.set_long("verbose");
+
+ let args = vec![make_masked("/VERBOSE", 0)];
+ let result = FlagMatcher::on_match_one(&args, &WINDOWS_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_windows_style_no_match_on_unrelated_flag() {
+ // Different flag should not match
+ let mut info = PickerArgInfo::new();
+ info.set_long("verbose");
+
+ let args = vec![make_masked("/output", 0)];
+ let result = FlagMatcher::on_match_one(&args, &WINDOWS_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+#[test]
+fn test_powershell_style_match() {
+ // PowerShell style: -Verbose (case insensitive)
+ let mut info = PickerArgInfo::new();
+ info.set_long("Verbose");
+
+ let args = vec![make_masked("-Verbose", 0)];
+ let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_powershell_style_match_case_insensitive() {
+ let mut info = PickerArgInfo::new();
+ info.set_long("Verbose");
+
+ let args = vec![make_masked("-VERBOSE", 0)];
+ let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
+ assert_eq!(result, Some(0));
+}
+
+#[test]
+fn test_unix_style_case_sensitive_no_match() {
+ // UNIX style is case-sensitive: --VERBOSE should NOT match --verbose
+ let mut info = PickerArgInfo::new();
+ info.set_long("verbose");
+
+ let args = vec![make_masked("--VERBOSE", 0)];
+ let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(result, None);
+}
+
+#[test]
+fn test_windows_style_match_all() {
+ // on_match_all should find all matching flags
+ let mut info = PickerArgInfo::new();
+ info.set_long("verbose");
+ info.set_short('v');
+
+ let args = vec![
+ make_masked("/v", 0),
+ make_masked("/output", 1),
+ make_masked("/VERBOSE", 2),
+ ];
+ let result = FlagMatcher::on_match_all(&args, &WINDOWS_STYLE, &info);
+ assert_eq!(result, vec![0, 2]);
+}
+
+#[test]
+fn test_windows_style_match_after_end_of_options() {
+ // end_of_options is always "--" regardless of style
+ // Flags after -- should not match
+ let mut info = PickerArgInfo::new();
+ info.set_long("verbose");
+
+ let args = vec![
+ make_masked("/verbose", 0),
+ make_masked("--", 1),
+ make_masked("/verbose", 2),
+ ];
+ let result = FlagMatcher::on_match_all(&args, &WINDOWS_STYLE, &info);
+ // end_of_options is always "--" regardless of style
+ assert_eq!(result, vec![0]);
+}
+
+// Naming case conversion
+
+/// A Unix-like style with kebab-case naming.
+const KEBAB_STYLE: ParserStyle<'static> = ParserStyle {
+ naming_case: ParserStyleNamingCase::Kebab,
+ ..UNIX_STYLE
+};
+
+#[test]
+fn test_kebab_case_naming_for_multiword_flag() {
+ // flag name `flag_a` → Kebab → `flag-a` → `--flag-a`
+ let mut info = PickerArgInfo::new();
+ info.set_long("flag_a");
+
+ let args = vec![make_masked("--flag-a", 0)];
+ let result = FlagMatcher::on_match_one(&args, &KEBAB_STYLE, &info);
+ assert_eq!(
+ result,
+ Some(0),
+ "--flag-a should match flag_a via kebab-case conversion"
+ );
+}
+
+#[test]
+fn test_snake_case_should_not_match_as_long_flag() {
+ // With kebab naming, `--flag_a` (snake) should NOT match `flag_a`
+ let mut info = PickerArgInfo::new();
+ info.set_long("flag_a");
+
+ let args = vec![make_masked("--flag_a", 0)];
+ let result = FlagMatcher::on_match_one(&args, &KEBAB_STYLE, &info);
+ assert_eq!(
+ result, None,
+ "--flag_a should NOT match in kebab-style context"
+ );
+}
+
+#[test]
+fn test_kebab_case_naming_for_unix_style() {
+ // UNIX_STYLE now uses Kebab case: `flag_a` → `flag-a` → `--flag-a`
+ let mut info = PickerArgInfo::new();
+ info.set_long("flag_a");
+
+ let args = vec![make_masked("--flag-a", 0)];
+ let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(
+ result,
+ Some(0),
+ "--flag-a should match with kebab-style UNIX_STYLE"
+ );
+}
+
+#[test]
+fn test_snake_case_rejected_by_unix_style() {
+ // UNIX_STYLE uses Kebab: `--flag_a` (snake) should NOT match
+ let mut info = PickerArgInfo::new();
+ info.set_long("flag_a");
+
+ let args = vec![make_masked("--flag_a", 0)];
+ let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(
+ result, None,
+ "--flag_a should NOT match under kebab-style UNIX_STYLE"
+ );
+}
+
+#[test]
+fn test_powershell_pascal_case_naming() {
+ // POWERSHELL_STYLE uses Pascal case: `verbose` → `Verbose` → `-Verbose`
+ let mut info = PickerArgInfo::new();
+ info.set_long("verbose");
+
+ let args = vec![make_masked("-Verbose", 0)];
+ let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
+ assert_eq!(
+ result,
+ Some(0),
+ "-Verbose should match verbose via Pascal case"
+ );
+}
+
+#[test]
+fn test_flag_naming_kebab_matches_my_name() {
+ // UNIX_STYLE now uses Kebab: `my_name` → `my-name` → `--my-name`
+ let mut info = PickerArgInfo::new();
+ info.set_long("my_name");
+
+ let args = vec![make_masked("--my-name", 0)];
+ let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(
+ result,
+ Some(0),
+ "--my-name should match via kebab conversion"
+ );
+}
+
+#[test]
+fn test_flag_naming_kebab_rejects_my_name_underscore() {
+ // Kebab style: `--my_name` (snake) should NOT match
+ let mut info = PickerArgInfo::new();
+ info.set_long("my_name");
+
+ let args = vec![make_masked("--my_name", 0)];
+ let result = FlagMatcher::on_match_one(&args, &UNIX_STYLE, &info);
+ assert_eq!(
+ result, None,
+ "--my_name should NOT match under kebab-style UNIX_STYLE"
+ );
+}
+
+#[test]
+fn test_flag_naming_pascal_matches_my_name() {
+ // `my_name` under Pascal → `MyName` → `-MyName`
+ let mut info = PickerArgInfo::new();
+ info.set_long("my_name");
+
+ let args = vec![make_masked("-MyName", 0)];
+ let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
+ assert_eq!(
+ result,
+ Some(0),
+ "-MyName should match via Pascal conversion"
+ );
+}
+
+#[test]
+fn test_flag_naming_pascal_matches_lowercase() {
+ // PowerShell is case-insensitive: `-myname` should also match
+ let mut info = PickerArgInfo::new();
+ info.set_long("my_name");
+
+ let args = vec![make_masked("-myname", 0)];
+ let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
+ assert_eq!(
+ result,
+ Some(0),
+ "-myname (lowercase) should match via case-insensitive Pascal"
+ );
+}
+
+#[test]
+fn test_flag_naming_pascal_rejects_my_name_underscore() {
+ // Pascal style: `-my_name` (underscore) should NOT match
+ let mut info = PickerArgInfo::new();
+ info.set_long("my_name");
+
+ let args = vec![make_masked("-my_name", 0)];
+ let result = FlagMatcher::on_match_one(&args, &POWERSHELL_STYLE, &info);
+ assert_eq!(
+ result, None,
+ "-my_name should NOT match under Pascal-style naming"
+ );
+}
diff --git a/mingling_picker/test/src/test/value_flag_test.rs b/mingling_picker/test/src/test/value_flag_test.rs
new file mode 100644
index 0000000..d267a27
--- /dev/null
+++ b/mingling_picker/test/src/test/value_flag_test.rs
@@ -0,0 +1,174 @@
+use mingling_picker::value::Flag;
+use mingling_picker::{IntoPicker, macros::arg};
+
+// Basic Flag — present / absent
+
+#[test]
+fn test_flag_present() {
+ let flag: Flag = vec!["--verbose"]
+ .to_picker()
+ .pick(&arg![verbose: Flag])
+ .unwrap();
+ assert_eq!(flag, Flag::Active);
+}
+
+#[test]
+fn test_flag_absent_returns_inactive() {
+ // Unlike bool, Flag::pick returns Parsed(Inactive) when no match is found,
+ // so or_default() is NOT required — unwrap() works directly.
+ let flag: Flag = Vec::<&str>::new()
+ .to_picker()
+ .pick(&arg![verbose: Flag])
+ .unwrap();
+ assert_eq!(flag, Flag::Inactive);
+}
+
+// Short Flag
+
+#[test]
+fn test_flag_short_present() {
+ let flag: Flag = vec!["-v"]
+ .to_picker()
+ .pick(&arg![verbose: Flag, 'v'])
+ .unwrap();
+ assert_eq!(flag, Flag::Active);
+}
+
+// Multiple Flags
+
+#[test]
+fn test_two_flags_both_present() {
+ let (a, b): (Flag, Flag) = vec!["--flag-a", "--flag-b"]
+ .to_picker()
+ .pick(&arg![flag_a: Flag])
+ .pick(&arg![flag_b: Flag])
+ .unwrap();
+ assert_eq!(a, Flag::Active);
+ assert_eq!(b, Flag::Active);
+}
+
+#[test]
+fn test_two_flags_one_present() {
+ let (a, b): (Flag, Flag) = vec!["--flag-a"]
+ .to_picker()
+ .pick(&arg![flag_a: Flag])
+ .pick(&arg![flag_b: Flag])
+ .unwrap();
+ assert_eq!(a, Flag::Active);
+ assert_eq!(b, Flag::Inactive);
+}
+
+#[test]
+fn test_two_flags_neither_present() {
+ let (a, b): (Flag, Flag) = Vec::<&str>::new()
+ .to_picker()
+ .pick(&arg![flag_a: Flag])
+ .pick(&arg![flag_b: Flag])
+ .unwrap();
+ assert_eq!(a, Flag::Inactive);
+ assert_eq!(b, Flag::Inactive);
+}
+
+// After `--` (end-of-options)
+
+#[test]
+fn test_flag_after_end_of_options() {
+ let flag: Flag = vec!["--", "--verbose"]
+ .to_picker()
+ .pick(&arg![verbose: Flag])
+ .unwrap();
+ assert_eq!(flag, Flag::Inactive);
+}
+
+// Alias
+
+#[test]
+fn test_flag_with_alias() {
+ let flag: Flag = vec!["--cfg"]
+ .to_picker()
+ .pick(&arg![config: Flag, "cfg"])
+ .unwrap();
+ assert_eq!(flag, Flag::Active);
+}
+
+#[test]
+fn test_flag_primary_name() {
+ let flag: Flag = vec!["--config"]
+ .to_picker()
+ .pick(&arg![config: Flag, "cfg"])
+ .unwrap();
+ assert_eq!(flag, Flag::Active);
+}
+
+// Unrelated flag should not match
+
+#[test]
+fn test_unrelated_flag_does_not_match() {
+ let flag: Flag = vec!["--other"]
+ .to_picker()
+ .pick(&arg![verbose: Flag])
+ .unwrap();
+ assert_eq!(flag, Flag::Inactive);
+}
+
+// to_result / to_option
+
+#[test]
+fn test_flag_to_result() {
+ let result: Result<Flag, ()> = vec!["--verbose"]
+ .to_picker()
+ .pick(&arg![verbose: Flag])
+ .to_result();
+ assert_eq!(result, Ok(Flag::Active));
+}
+
+#[test]
+fn test_flag_to_option() {
+ let opt: Option<Flag> = vec!["--verbose"]
+ .to_picker()
+ .pick(&arg![verbose: Flag])
+ .to_option();
+ assert_eq!(opt, Some(Flag::Active));
+}
+
+// Bool conversions
+
+#[test]
+fn test_flag_converts_to_bool() {
+ let flag = Flag::Active;
+ assert!(bool::from(flag));
+
+ let flag = Flag::Inactive;
+ assert!(!bool::from(flag));
+}
+
+#[test]
+fn test_flag_from_bool() {
+ assert_eq!(Flag::from(true), Flag::Active);
+ assert_eq!(Flag::from(false), Flag::Inactive);
+}
+
+#[test]
+fn test_flag_deref_to_bool() {
+ let active = Flag::Active;
+ assert!(*active);
+
+ let inactive = Flag::Inactive;
+ assert!(!*inactive);
+}
+
+// Flag never triggers route (unlike bool)
+//
+// Flag::pick always returns Parsed, so the fallback chain
+// (default → route) is never entered.
+
+#[test]
+fn test_flag_absent_does_not_trigger_route() {
+ // Even without or_default / or_route, absent flag returns Inactive, not a route
+ let result: Result<Flag, &str> = Vec::<&str>::new()
+ .with_route::<&str>()
+ .pick(&arg![verbose: Flag])
+ .or_route(|| "should_not_fire")
+ .to_result();
+ assert_eq!(result, Ok(Flag::Inactive));
+}
diff --git a/mingling_picker/test/src/test/value_string_test.rs b/mingling_picker/test/src/test/value_string_test.rs
new file mode 100644
index 0000000..b1e5c0b
--- /dev/null
+++ b/mingling_picker/test/src/test/value_string_test.rs
@@ -0,0 +1,171 @@
+use mingling_picker::{IntoPicker, macros::arg};
+
+// Basic named String — present / absent
+
+#[test]
+fn test_string_named_present() {
+ let val: String = vec!["--name", "Alice"]
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .unwrap();
+ assert_eq!(val, "Alice");
+}
+
+#[test]
+fn test_string_named_absent_uses_default() {
+ let val: String = Vec::<&str>::new()
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .unwrap();
+ assert_eq!(val, "");
+}
+
+// Named String — eq mode
+
+#[test]
+fn test_string_named_eq_mode() {
+ let val: String = vec!["--name=Alice"]
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .unwrap();
+ assert_eq!(val, "Alice");
+}
+
+// Named String — short flag
+
+#[test]
+fn test_string_named_short_flag() {
+ let val: String = vec!["-n", "Alice"]
+ .to_picker()
+ .pick(&arg![name: String, 'n'])
+ .or_default()
+ .unwrap();
+ assert_eq!(val, "Alice");
+}
+
+// Named String — no value after flag
+
+#[test]
+fn test_string_named_missing_value_triggers_default() {
+ // --name at end with no following arg → pick returns NotFound → or_default gives ""
+ let val: String = vec!["--name"]
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .unwrap();
+ assert_eq!(val, "");
+}
+
+// Positional String
+
+#[test]
+fn test_string_positional() {
+ let val: String = vec!["file.txt"]
+ .to_picker()
+ .pick(&arg![String])
+ .or_default()
+ .unwrap();
+ assert_eq!(val, "file.txt");
+}
+
+#[test]
+fn test_string_positional_takes_first() {
+ let val: String = vec!["first", "second"]
+ .to_picker()
+ .pick(&arg![String])
+ .or_default()
+ .unwrap();
+ assert_eq!(val, "first");
+}
+
+// Multiple occurrences (Single only tags one occurrence per Parseable)
+
+#[test]
+fn test_string_two_named_flags() {
+ let (a, b): (String, String) = vec!["--name", "Alice", "--greeting", "Hello"]
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .pick(&arg![greeting: String])
+ .or_default()
+ .unwrap();
+ assert_eq!(a, "Alice");
+ assert_eq!(b, "Hello");
+}
+
+// Mixed named + positional
+
+#[test]
+fn test_string_named_and_positional() {
+ let (name, file): (String, String) = vec!["--name", "Alice", "file.txt"]
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .pick(&arg![String])
+ .or_default()
+ .unwrap();
+ assert_eq!(name, "Alice");
+ assert_eq!(file, "file.txt");
+}
+
+// After `--` (end-of-options)
+
+#[test]
+fn test_string_named_after_end_of_options() {
+ // Named arg after `--` should not be matched → default
+ let val: String = vec!["--", "--name", "Alice"]
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .unwrap();
+ assert_eq!(val, "");
+}
+
+// Unrelated flag should not match → default
+
+#[test]
+fn test_string_unrelated_flag() {
+ let val: String = vec!["--other", "value"]
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .unwrap();
+ assert_eq!(val, "");
+}
+
+// to_result / to_option
+
+#[test]
+fn test_string_to_result() {
+ let result: Result<String, ()> = vec!["--name", "Alice"]
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .to_result();
+ assert_eq!(result, Ok("Alice".to_string()));
+}
+
+#[test]
+fn test_string_to_option() {
+ let opt: Option<String> = vec!["--name", "Alice"]
+ .to_picker()
+ .pick(&arg![name: String])
+ .or_default()
+ .to_option();
+ assert_eq!(opt, Some("Alice".to_string()));
+}
+
+// Custom default via .or()
+
+#[test]
+fn test_string_custom_default() {
+ let val: String = Vec::<&str>::new()
+ .to_picker()
+ .pick(&arg![name: String])
+ .or(|| "default_name".to_string())
+ .unwrap();
+ assert_eq!(val, "default_name");
+}