feat: Add new system for multi-selections.
This commit is contained in:
@@ -92,12 +92,15 @@ fn gen_headless(case: &TestCase, fixtures: &Fixtures) -> syn::Result<TokenStream
|
||||
// Build script string from actions
|
||||
let script = build_headless_script(&case.actions);
|
||||
|
||||
// Build extra CLI args (e.g. --label-key)
|
||||
let extra_args: Vec<TokenStream> = if let Some(ref key) = fixtures.label_key {
|
||||
vec![quote! { "--label-key" }, quote! { #key }]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
// Build extra CLI args (e.g. --label-key, --multi)
|
||||
let mut extra_args: Vec<TokenStream> = Vec::new();
|
||||
if let Some(ref key) = fixtures.label_key {
|
||||
extra_args.push(quote! { "--label-key" });
|
||||
extra_args.push(quote! { #key });
|
||||
}
|
||||
if fixtures.multi == Some(true) {
|
||||
extra_args.push(quote! { "--multi" });
|
||||
}
|
||||
|
||||
// Build assertions
|
||||
let mut asserts = Vec::new();
|
||||
@@ -327,7 +330,9 @@ fn gen_menu(case: &TestCase, fixtures: &Fixtures) -> syn::Result<TokenStream> {
|
||||
} else if let Some(ref expected) = case.selected {
|
||||
quote! {
|
||||
match &result {
|
||||
Ok(MenuResult::Selected { value, .. }) => {
|
||||
Ok(MenuResult::Selected { items, .. }) => {
|
||||
assert!(!items.is_empty(), "expected Selected with items, got empty");
|
||||
let value = &items[0].0;
|
||||
let got = value.as_str()
|
||||
.or_else(|| value.get(#label_key).and_then(|v| v.as_str()))
|
||||
.unwrap_or("");
|
||||
@@ -339,6 +344,25 @@ fn gen_menu(case: &TestCase, fixtures: &Fixtures) -> syn::Result<TokenStream> {
|
||||
other => panic!("expected Selected, got: {:?}", other),
|
||||
}
|
||||
}
|
||||
} else if let Some(ref expected) = case.selected_items {
|
||||
quote! {
|
||||
match &result {
|
||||
Ok(MenuResult::Selected { items, .. }) => {
|
||||
let mut labels: Vec<&str> = items.iter()
|
||||
.filter_map(|(v, _)| v.as_str()
|
||||
.or_else(|| v.get(#label_key).and_then(|v| v.as_str())))
|
||||
.collect();
|
||||
labels.sort();
|
||||
let mut expected = vec![#(#expected),*];
|
||||
expected.sort();
|
||||
assert_eq!(
|
||||
labels, expected,
|
||||
"expected selected_items {:?}, got {:?}", expected, labels
|
||||
);
|
||||
}
|
||||
other => panic!("expected Selected, got: {:?}", other),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No assertion on result. Probably an error, but let it compile.
|
||||
quote! {}
|
||||
@@ -348,11 +372,18 @@ fn gen_menu(case: &TestCase, fixtures: &Fixtures) -> syn::Result<TokenStream> {
|
||||
// we need to drop tx after sending actions.
|
||||
let drop_sender = quote! { drop(tx); };
|
||||
|
||||
let multi_setup = if fixtures.multi == Some(true) {
|
||||
quote! { menu.set_multi(true); }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[tokio::test]
|
||||
async fn #test_name() {
|
||||
let items = vec![#(#item_exprs),*];
|
||||
let (menu, tx) = MenuRunner::new(JsonMenu::new(items, #label_key.to_string()));
|
||||
let (mut menu, tx) = MenuRunner::new(JsonMenu::new(items, #label_key.to_string()));
|
||||
#multi_setup
|
||||
let mut rx = menu.subscribe();
|
||||
let handle = tokio::spawn(async move { menu.run().await });
|
||||
|
||||
@@ -427,6 +458,12 @@ fn menu_action_variant(name: &str) -> syn::Result<TokenStream> {
|
||||
"half-page-down" => quote! { Action::HalfPageDown(1) },
|
||||
"set-mode-insert" => quote! { Action::SetMode(pikl_core::event::Mode::Insert) },
|
||||
"set-mode-normal" => quote! { Action::SetMode(pikl_core::event::Mode::Normal) },
|
||||
"set-mode-visual" => quote! { Action::SetMode(pikl_core::event::Mode::Visual) },
|
||||
"toggle-select" => quote! { Action::ToggleSelect },
|
||||
"select-all" => quote! { Action::SelectAll },
|
||||
"clear-selections" => quote! { Action::ClearSelections },
|
||||
"undo-selection" => quote! { Action::UndoSelection },
|
||||
"redo-selection" => quote! { Action::RedoSelection },
|
||||
_ => {
|
||||
return Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
|
||||
@@ -31,6 +31,7 @@ pub struct Fixtures {
|
||||
pub items: Option<Vec<String>>,
|
||||
pub label_key: Option<String>,
|
||||
pub viewport: Option<(usize, usize)>, // (height, count)
|
||||
pub multi: Option<bool>,
|
||||
}
|
||||
|
||||
pub struct TestCase {
|
||||
@@ -44,6 +45,7 @@ pub struct TestCase {
|
||||
pub cursor: Option<usize>,
|
||||
pub offset: Option<usize>,
|
||||
pub selected: Option<String>,
|
||||
pub selected_items: Option<Vec<String>>,
|
||||
pub cancelled: bool,
|
||||
}
|
||||
|
||||
@@ -89,6 +91,7 @@ impl Parse for TestModule {
|
||||
items: None,
|
||||
label_key: None,
|
||||
viewport: None,
|
||||
multi: None,
|
||||
};
|
||||
let mut tests = Vec::new();
|
||||
|
||||
@@ -120,11 +123,18 @@ impl Parse for TestModule {
|
||||
fixtures.viewport = Some(parse_viewport_def(&content)?);
|
||||
eat_semi(&content);
|
||||
}
|
||||
"multi" => {
|
||||
consume_ident_or_keyword(&content)?;
|
||||
content.parse::<Token![:]>()?;
|
||||
let val: syn::LitBool = content.parse()?;
|
||||
fixtures.multi = Some(val.value);
|
||||
eat_semi(&content);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new(
|
||||
content.span(),
|
||||
format!(
|
||||
"unexpected field '{ident_str}', expected test, items, label_key, or viewport"
|
||||
"unexpected field '{ident_str}', expected test, items, label_key, viewport, or multi"
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -163,6 +173,7 @@ fn parse_test_case(input: ParseStream) -> syn::Result<TestCase> {
|
||||
cursor: None,
|
||||
offset: None,
|
||||
selected: None,
|
||||
selected_items: None,
|
||||
cancelled: false,
|
||||
};
|
||||
|
||||
@@ -224,6 +235,10 @@ fn parse_test_case(input: ParseStream) -> syn::Result<TestCase> {
|
||||
let val: LitStr = content.parse()?;
|
||||
case.selected = Some(val.value());
|
||||
}
|
||||
"selected_items" => {
|
||||
content.parse::<Token![:]>()?;
|
||||
case.selected_items = Some(parse_string_list(&content)?);
|
||||
}
|
||||
"cancelled" => {
|
||||
// Just the keyword presence means true. Optionally parse `: true`.
|
||||
if content.peek(Token![:]) {
|
||||
|
||||
Reference in New Issue
Block a user