refactor(command): unify minibuffer commits into CommitBuffer(kind) (improvise-6t3)
CommitFormula, CommitCategoryAdd and CommitItemAdd collapse into a single
CommitBuffer { kind: CommitKind }, registered as
`commit-buffer <formula|category|item>`. The five keymap bindings pass
the kind as an argument, giving improvise-w3q a uniform primitive to
build its text-entry template on.
The three did not have identical shape — they differed in buffer name,
empty-buffer landing mode, post-commit landing mode, status text and add
effect, and two of those are semantic (formula returns to its panel;
category/item stay open for batch entry). CommitKind carries those as
methods rather than flattening them, so this costs ~24 lines rather than
saving 40. The win is one home for minibuffer commit semantics.
CommitKind::Item carries its target category, resolved from
AppMode::ItemAdd by parse_commit_kind at registry time. execute() is
therefore a pure function of kind + buffer, the shared helper's
Option<Box<dyn Effect>> is gone, and the old silent no-op outside
ItemAdd mode becomes a real error on the `:` line.
Fixes a bug found on the way: the commit pushed set_status("Formula
added") after AddFormula, clobbering the parse error that
AddFormula::apply writes to the status bar — an unparseable formula was
dropped while the UI reported success. The add effect now goes last, so
the only party that knows whether it worked gets the last word.
Also adds a keymap test that dispatches Enter in each minibuffer mode:
Keymap::dispatch swallows an unresolvable command name with .ok()?, so
the existing lookup-only smoke tests would have passed even had the
rename missed the registry.
Filed improvise-djn: AddCategory still swallows its Result.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+5
-4
@@ -145,7 +145,8 @@ Region=West, Measure=Revenue = |pending| # pipe-quoted text
|
||||
10. **Float equality inside formula eval is currently mixed** — `=`/`!=` use a `1e-10` epsilon but the div-by-zero guard checks `rv == 0.0` exactly. Don't assume one or the other; tracked for cleanup.
|
||||
11. **`IndexMap`** preserves insertion order for categories, views, and items. Persistence relies on this.
|
||||
12. **Commit paths for cell edits** split across `commit_cell_value` → `commit_regular_cell_value` / `commit_plain_records_edit` in `command/cmd/commit.rs`. Keep the synthetic-records branch in sync with the plain branch when changing behavior.
|
||||
13. **`app.layout` goes stale mid-batch.** `apply_effects` rebuilds only *after* the whole batch, so an effect that reads the layout after an earlier effect changed the grid needs a `RebuildLayout` effect in front of it — the command decides the order (see `EnterEditAtCursorCmd`, `CommitAndAdvance`). Read the cursor's cell text via `App::display_value_at_cursor()`; don't build a `CmdContext` inside `apply` (it recomputes column widths).
|
||||
13. **Minibuffer commits go through one command.** `commit-buffer <formula|category|item>` (`CommitBuffer { kind: CommitKind }`) owns buffer→trim→bail-on-empty→dirty→status for all three; the varying parts are methods on `CommitKind`. The add effect is emitted **last** so its failure message wins over the command's optimistic status (improvise-6t3). `CommitKind::Item` carries its target category, resolved from `AppMode::ItemAdd` by `parse_commit_kind` at registry time — so `commit-buffer item` is not constructible from a script line.
|
||||
14. **`app.layout` goes stale mid-batch.** `apply_effects` rebuilds only *after* the whole batch, so an effect that reads the layout after an earlier effect changed the grid needs a `RebuildLayout` effect in front of it — the command decides the order (see `EnterEditAtCursorCmd`, `CommitAndAdvance`). Read the cursor's cell text via `App::display_value_at_cursor()`; don't build a `CmdContext` inside `apply` (it recomputes column widths).
|
||||
|
||||
---
|
||||
|
||||
@@ -184,10 +185,10 @@ import/csv_parser.rs 300 / 8t CSV parsing, multi-file merge
|
||||
### Command layer (`crates/improvise-command/src/command/`)
|
||||
```
|
||||
cmd/core.rs 319 / 2t Cmd trait, CmdContext, CmdRegistry, parse helpers
|
||||
cmd/registry.rs 601 / 0t default_registry() — all registrations (no tests yet)
|
||||
cmd/registry.rs 604 / 0t default_registry() — all registrations (no tests yet)
|
||||
cmd/navigation.rs 501 / 10t Move, EnterAdvance, Page*
|
||||
cmd/cell.rs 198 / 6t ClearCell, YankCell, PasteCell, TransposeAxes, SaveCmd
|
||||
cmd/commit.rs 455 / 7t CommitFormula, CommitCategoryAdd/ItemAdd, CommitExport
|
||||
cmd/commit.rs 517 / 9t CommitAndAdvance, CommitBuffer(CommitKind), CommitExport
|
||||
cmd/effect_cmds.rs 466 / 5t effect_cmd! macro, 25+ simple wrappers
|
||||
cmd/grid.rs 494 / 7t ToggleGroup, ViewNavigate, DrillIntoCell, TogglePruneEmpty
|
||||
cmd/mode.rs 354 / 8t EnterMode, Quit, EditOrDrill, EnterTileSelect
|
||||
@@ -196,7 +197,7 @@ cmd/search.rs 202 / 4t SearchNavigate, SearchOrCategoryAdd, ExitSea
|
||||
cmd/text_buffer.rs 240 / 7t AppendChar, PopChar, CommandModeBackspace, ExecuteCommand
|
||||
cmd/tile.rs 160 / 5t MoveTileCursor, TileAxisOp
|
||||
cmd/wizard.rs 199 / 4t HandleWizardKey — per-step import-wizard key tables
|
||||
keymap.rs 1248 / 22t KeyPattern, Binding, Keymap, ModeKey, 16 mode keymaps
|
||||
keymap.rs 1290 / 23t KeyPattern, Binding, Keymap, ModeKey, 16 mode keymaps
|
||||
parse.rs 236 / 19t Script/command-line parser (prefix syntax)
|
||||
```
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::model::cell::{CellKey, CellValue};
|
||||
use crate::ui::app::AppMode;
|
||||
use crate::ui::effect::{self, Effect};
|
||||
|
||||
use super::core::{Cmd, CmdContext};
|
||||
use super::core::{Cmd, CmdContext, read_buffer};
|
||||
use super::grid::AddRecordRow;
|
||||
use super::navigation::{CursorState, EnterAdvance, viewport_effects};
|
||||
|
||||
@@ -14,6 +14,10 @@ mod tests {
|
||||
use crate::command::cmd::test_helpers::*;
|
||||
use crate::workbook::Workbook;
|
||||
|
||||
fn commit(kind: CommitKind) -> CommitBuffer {
|
||||
CommitBuffer { kind }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commit_formula_with_categories_adds_formula() {
|
||||
let m = two_cat_model();
|
||||
@@ -23,7 +27,7 @@ mod tests {
|
||||
bufs.insert("formula".to_string(), "Profit = Revenue - Cost".to_string());
|
||||
let mut ctx = make_ctx(&m, &layout, ®);
|
||||
ctx.buffers = &bufs;
|
||||
let effects = CommitFormula.execute(&ctx);
|
||||
let effects = commit(CommitKind::Formula).execute(&ctx);
|
||||
let dbg = effects_debug(&effects);
|
||||
assert!(
|
||||
dbg.contains("AddFormula"),
|
||||
@@ -46,7 +50,7 @@ mod tests {
|
||||
bufs.insert("formula".to_string(), "X = Y + Z".to_string());
|
||||
let mut ctx = make_ctx(&m, &layout, ®);
|
||||
ctx.buffers = &bufs;
|
||||
let effects = CommitFormula.execute(&ctx);
|
||||
let effects = commit(CommitKind::Formula).execute(&ctx);
|
||||
let dbg = effects_debug(&effects);
|
||||
assert!(
|
||||
dbg.contains("AddFormula"),
|
||||
@@ -58,6 +62,33 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// improvise-6t3: the commit pushed `set_status("Formula added")` *after*
|
||||
/// `AddFormula`, so the parse error that `AddFormula::apply` writes to the
|
||||
/// status bar was immediately overwritten. An unparseable formula was
|
||||
/// dropped while the UI reported success.
|
||||
///
|
||||
/// The add effect is the only party that knows whether it worked, so it has
|
||||
/// to get the last word on the status line.
|
||||
#[test]
|
||||
fn invalid_formula_reports_the_parse_error_not_success() {
|
||||
let m = two_cat_model();
|
||||
let layout = make_layout(&m);
|
||||
let reg = make_registry();
|
||||
let mut bufs = HashMap::new();
|
||||
bufs.insert("formula".to_string(), "Profit = = ".to_string());
|
||||
let mut ctx = make_ctx(&m, &layout, ®);
|
||||
ctx.buffers = &bufs;
|
||||
let effects = commit(CommitKind::Formula).execute(&ctx);
|
||||
|
||||
let mut app = crate::ui::app::App::new(two_cat_model(), None);
|
||||
app.apply_effects(effects);
|
||||
assert!(
|
||||
app.view_state.status_msg.contains("Formula error"),
|
||||
"Expected the parse error to reach the status bar, got: {:?}",
|
||||
app.view_state.status_msg
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commit_category_add_with_name_produces_add_effect() {
|
||||
let m = two_cat_model();
|
||||
@@ -67,7 +98,7 @@ mod tests {
|
||||
bufs.insert("category".to_string(), "Region".to_string());
|
||||
let mut ctx = make_ctx(&m, &layout, ®);
|
||||
ctx.buffers = &bufs;
|
||||
let effects = CommitCategoryAdd.execute(&ctx);
|
||||
let effects = commit(CommitKind::Category).execute(&ctx);
|
||||
let dbg = effects_debug(&effects);
|
||||
assert!(
|
||||
dbg.contains("AddCategory"),
|
||||
@@ -84,7 +115,7 @@ mod tests {
|
||||
bufs.insert("category".to_string(), "".to_string());
|
||||
let mut ctx = make_ctx(&m, &layout, ®);
|
||||
ctx.buffers = &bufs;
|
||||
let effects = CommitCategoryAdd.execute(&ctx);
|
||||
let effects = commit(CommitKind::Category).execute(&ctx);
|
||||
let dbg = effects_debug(&effects);
|
||||
assert!(
|
||||
dbg.contains("CategoryPanel"),
|
||||
@@ -92,8 +123,10 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// `commit-buffer item` takes its target category off `AppMode::ItemAdd`
|
||||
/// at construction time, so the command itself never reads the mode.
|
||||
#[test]
|
||||
fn commit_item_add_with_name_produces_add_item() {
|
||||
fn commit_item_add_files_the_item_under_the_mode_s_category() {
|
||||
let m = two_cat_model();
|
||||
let layout = make_layout(&m);
|
||||
let reg = make_registry();
|
||||
@@ -103,19 +136,24 @@ mod tests {
|
||||
let item_add_mode = AppMode::item_add("Month".to_string());
|
||||
ctx.mode = &item_add_mode;
|
||||
ctx.buffers = &bufs;
|
||||
let effects = CommitItemAdd.execute(&ctx);
|
||||
let dbg = effects_debug(&effects);
|
||||
let cmd = reg
|
||||
.interactive("commit-buffer", &["item".to_string()], &ctx)
|
||||
.expect("item-add mode should supply the category");
|
||||
let dbg = effects_debug(&cmd.execute(&ctx));
|
||||
assert!(dbg.contains("AddItem"), "Expected AddItem, got: {dbg}");
|
||||
assert!(dbg.contains("Month"), "Expected category Month, got: {dbg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commit_item_add_outside_item_add_mode_returns_empty() {
|
||||
fn commit_item_add_outside_item_add_mode_is_a_construction_error() {
|
||||
let m = two_cat_model();
|
||||
let layout = make_layout(&m);
|
||||
let reg = make_registry();
|
||||
let ctx = make_ctx(&m, &layout, ®);
|
||||
let effects = CommitItemAdd.execute(&ctx);
|
||||
assert!(effects.is_empty());
|
||||
assert!(
|
||||
reg.interactive("commit-buffer", &["item".to_string()], &ctx)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
/// `CommitAndAdvance` must thread its `edit_mode` through to the
|
||||
@@ -349,95 +387,118 @@ impl Cmd for CommitAndAdvance {
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit a formula from the formula edit buffer.
|
||||
#[derive(Debug)]
|
||||
pub struct CommitFormula;
|
||||
impl Cmd for CommitFormula {
|
||||
fn name(&self) -> &'static str {
|
||||
"commit-formula"
|
||||
/// What a minibuffer commit produces. The variants differ only in the model
|
||||
/// mutation they build and in where they leave the user — reading the buffer,
|
||||
/// trimming it, bailing out when it is empty, marking the model dirty and
|
||||
/// reporting are shared by [`CommitBuffer`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CommitKind {
|
||||
Formula,
|
||||
Category,
|
||||
/// Add an item to this category. Resolved from [`AppMode::ItemAdd`] when
|
||||
/// the command is constructed, so `execute` stays a pure function of the
|
||||
/// kind and its buffer.
|
||||
Item {
|
||||
category: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Resolve a `commit-buffer` argument against the current mode. `Item` is the
|
||||
/// only kind that needs anything from the mode — its target category — so a
|
||||
/// script line (which has no live mode) can build the other two.
|
||||
pub fn parse_commit_kind(arg: &str, mode: &AppMode) -> Result<CommitKind, String> {
|
||||
match (arg, mode) {
|
||||
("formula", _) => Ok(CommitKind::Formula),
|
||||
("category", _) => Ok(CommitKind::Category),
|
||||
("item", AppMode::ItemAdd { category, .. }) => Ok(CommitKind::Item {
|
||||
category: category.clone(),
|
||||
}),
|
||||
("item", _) => Err("commit-buffer item requires item-add mode".into()),
|
||||
(other, _) => Err(format!("Unknown commit kind: {other}")),
|
||||
}
|
||||
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
||||
let buf = ctx.buffers.get("formula").cloned().unwrap_or_default();
|
||||
// Default formula target to _Measure (the virtual measure category).
|
||||
// _Measure dynamically includes all formula targets.
|
||||
vec![
|
||||
Box::new(effect::AddFormula {
|
||||
raw: buf,
|
||||
}
|
||||
|
||||
impl CommitKind {
|
||||
/// The minibuffer this kind consumes.
|
||||
pub fn buffer(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Formula => "formula",
|
||||
Self::Category => "category",
|
||||
Self::Item { .. } => "item",
|
||||
}
|
||||
}
|
||||
|
||||
/// Where an empty buffer lands the user: back in the panel that opened the
|
||||
/// minibuffer.
|
||||
fn on_empty(&self) -> AppMode {
|
||||
match self {
|
||||
Self::Formula => AppMode::FormulaPanel,
|
||||
Self::Category | Self::Item { .. } => AppMode::CategoryPanel,
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a *successful* commit lands the user. `None` keeps the minibuffer
|
||||
/// open for the next entry — categories and items are entered in batches,
|
||||
/// formulas are not.
|
||||
fn on_commit(&self) -> Option<AppMode> {
|
||||
match self {
|
||||
Self::Formula => Some(AppMode::FormulaPanel),
|
||||
Self::Category | Self::Item { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn status(&self, name: &str) -> String {
|
||||
match self {
|
||||
Self::Formula => "Formula added".to_string(),
|
||||
Self::Category => format!("Added category \"{name}\""),
|
||||
Self::Item { .. } => format!("Added \"{name}\""),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_effect(&self, name: &str) -> Box<dyn Effect> {
|
||||
match self {
|
||||
// Formulas default to _Measure, the virtual measure category, which
|
||||
// dynamically includes every formula target.
|
||||
Self::Formula => Box::new(effect::AddFormula {
|
||||
raw: name.to_string(),
|
||||
target_category: "_Measure".to_string(),
|
||||
}),
|
||||
effect::mark_dirty(),
|
||||
effect::set_status("Formula added"),
|
||||
effect::change_mode(AppMode::FormulaPanel),
|
||||
]
|
||||
Self::Category => Box::new(effect::AddCategory(name.to_string())),
|
||||
Self::Item { category } => Box::new(effect::AddItem {
|
||||
category: category.clone(),
|
||||
item: name.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared helper: read a buffer, trim it, and if non-empty, produce add + dirty
|
||||
/// + status effects. If empty, return to CategoryPanel.
|
||||
/// Commit the contents of a minibuffer as a formula, a category or an item.
|
||||
///
|
||||
/// Buffer clearing is handled by the keymap (Enter → [commit, clear-buffer]).
|
||||
fn commit_add_from_buffer(
|
||||
ctx: &CmdContext,
|
||||
buffer_name: &str,
|
||||
add_effect: impl FnOnce(&str) -> Option<Box<dyn Effect>>,
|
||||
status_msg: impl FnOnce(&str) -> String,
|
||||
) -> Vec<Box<dyn Effect>> {
|
||||
let buf = ctx.buffers.get(buffer_name).cloned().unwrap_or_default();
|
||||
let trimmed = buf.trim().to_string();
|
||||
if trimmed.is_empty() {
|
||||
return vec![effect::change_mode(AppMode::CategoryPanel)];
|
||||
}
|
||||
let Some(add) = add_effect(&trimmed) else {
|
||||
return vec![];
|
||||
};
|
||||
vec![
|
||||
add,
|
||||
effect::mark_dirty(),
|
||||
effect::set_status(status_msg(&trimmed)),
|
||||
]
|
||||
}
|
||||
|
||||
/// Commit adding a category, staying in CategoryAdd mode for the next entry.
|
||||
#[derive(Debug)]
|
||||
pub struct CommitCategoryAdd;
|
||||
impl Cmd for CommitCategoryAdd {
|
||||
pub struct CommitBuffer {
|
||||
pub kind: CommitKind,
|
||||
}
|
||||
impl Cmd for CommitBuffer {
|
||||
fn name(&self) -> &'static str {
|
||||
"commit-category-add"
|
||||
"commit-buffer"
|
||||
}
|
||||
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
||||
commit_add_from_buffer(
|
||||
ctx,
|
||||
"category",
|
||||
|name| Some(Box::new(effect::AddCategory(name.to_string()))),
|
||||
|name| format!("Added category \"{name}\""),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit adding an item, staying in ItemAdd mode for the next entry.
|
||||
#[derive(Debug)]
|
||||
pub struct CommitItemAdd;
|
||||
impl Cmd for CommitItemAdd {
|
||||
fn name(&self) -> &'static str {
|
||||
"commit-item-add"
|
||||
}
|
||||
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
|
||||
let category = if let AppMode::ItemAdd { category, .. } = ctx.mode {
|
||||
category.clone()
|
||||
} else {
|
||||
return vec![];
|
||||
};
|
||||
commit_add_from_buffer(
|
||||
ctx,
|
||||
"item",
|
||||
|name| {
|
||||
Some(Box::new(effect::AddItem {
|
||||
category: category.clone(),
|
||||
item: name.to_string(),
|
||||
}))
|
||||
},
|
||||
|name| format!("Added \"{name}\""),
|
||||
)
|
||||
let raw = read_buffer(ctx, self.kind.buffer());
|
||||
let name = raw.trim();
|
||||
if name.is_empty() {
|
||||
return vec![effect::change_mode(self.kind.on_empty())];
|
||||
}
|
||||
let mut effects: Vec<Box<dyn Effect>> = vec![
|
||||
effect::mark_dirty(),
|
||||
effect::set_status(self.kind.status(name)),
|
||||
// The add goes last on purpose: it is the only party that knows
|
||||
// whether it worked, so the optimistic status above must not
|
||||
// clobber the error it reports (improvise-6t3).
|
||||
self.kind.add_effect(name),
|
||||
];
|
||||
effects.extend(self.kind.on_commit().map(effect::change_mode));
|
||||
effects
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -573,9 +573,21 @@ pub fn default_registry() -> CmdRegistry {
|
||||
}))
|
||||
},
|
||||
);
|
||||
r.register_nullary(|| Box::new(CommitFormula));
|
||||
r.register_nullary(|| Box::new(CommitCategoryAdd));
|
||||
r.register_nullary(|| Box::new(CommitItemAdd));
|
||||
r.register(
|
||||
&CommitBuffer {
|
||||
kind: CommitKind::Category,
|
||||
},
|
||||
|args| {
|
||||
require_args("commit-buffer", args, 1)?;
|
||||
let kind = parse_commit_kind(&args[0], &AppMode::Normal)?;
|
||||
Ok(Box::new(CommitBuffer { kind }))
|
||||
},
|
||||
|args, ctx| {
|
||||
require_args("commit-buffer", args, 1)?;
|
||||
let kind = parse_commit_kind(&args[0], ctx.mode)?;
|
||||
Ok(Box::new(CommitBuffer { kind }))
|
||||
},
|
||||
);
|
||||
r.register_nullary(|| Box::new(CommitExport));
|
||||
r.register_nullary(|| Box::new(ExecuteCommand));
|
||||
|
||||
|
||||
@@ -842,7 +842,7 @@ impl KeymapSet {
|
||||
KeyCode::Enter,
|
||||
none,
|
||||
vec![
|
||||
("commit-formula", vec![]),
|
||||
("commit-buffer", vec!["formula".into()]),
|
||||
("clear-buffer", vec!["formula".into()]),
|
||||
],
|
||||
);
|
||||
@@ -864,7 +864,7 @@ impl KeymapSet {
|
||||
KeyCode::Enter,
|
||||
none,
|
||||
vec![
|
||||
("commit-category-add", vec![]),
|
||||
("commit-buffer", vec!["category".into()]),
|
||||
("clear-buffer", vec!["category".into()]),
|
||||
],
|
||||
);
|
||||
@@ -872,7 +872,7 @@ impl KeymapSet {
|
||||
KeyCode::Tab,
|
||||
none,
|
||||
vec![
|
||||
("commit-category-add", vec![]),
|
||||
("commit-buffer", vec!["category".into()]),
|
||||
("clear-buffer", vec!["category".into()]),
|
||||
],
|
||||
);
|
||||
@@ -899,7 +899,7 @@ impl KeymapSet {
|
||||
KeyCode::Enter,
|
||||
none,
|
||||
vec![
|
||||
("commit-item-add", vec![]),
|
||||
("commit-buffer", vec!["item".into()]),
|
||||
("clear-buffer", vec!["item".into()]),
|
||||
],
|
||||
);
|
||||
@@ -907,7 +907,7 @@ impl KeymapSet {
|
||||
KeyCode::Tab,
|
||||
none,
|
||||
vec![
|
||||
("commit-item-add", vec![]),
|
||||
("commit-buffer", vec!["item".into()]),
|
||||
("clear-buffer", vec!["item".into()]),
|
||||
],
|
||||
);
|
||||
@@ -1240,6 +1240,50 @@ mod tests {
|
||||
assert!(search.lookup(KeyCode::Esc, KeyModifiers::NONE).is_some());
|
||||
}
|
||||
|
||||
/// improvise-6t3: the three commit commands collapsed into a single
|
||||
/// `commit-buffer <kind>`. `dispatch` swallows an unresolvable command name
|
||||
/// with `.ok()?`, so a lookup-only assertion would still pass had the
|
||||
/// rename missed the registry — Enter has to come back with real effects.
|
||||
#[test]
|
||||
fn minibuffer_enter_dispatches_to_a_registered_commit() {
|
||||
use crate::command::cmd::test_helpers::*;
|
||||
use crate::ui::app::AppMode;
|
||||
|
||||
let ks = KeymapSet::default_keymaps();
|
||||
let wb = two_cat_model();
|
||||
let layout = make_layout(&wb);
|
||||
let reg = make_registry();
|
||||
|
||||
let mut bufs = HashMap::new();
|
||||
bufs.insert("formula".to_string(), "Profit = Revenue - Cost".to_string());
|
||||
bufs.insert("category".to_string(), "Region".to_string());
|
||||
bufs.insert("item".to_string(), "March".to_string());
|
||||
|
||||
let item_add = AppMode::item_add("Month".to_string());
|
||||
for (mode_key, mode, expected) in [
|
||||
(ModeKey::FormulaEdit, &AppMode::formula_edit(), "AddFormula"),
|
||||
(
|
||||
ModeKey::CategoryAdd,
|
||||
&AppMode::category_add(),
|
||||
"AddCategory",
|
||||
),
|
||||
(ModeKey::ItemAdd, &item_add, "AddItem"),
|
||||
] {
|
||||
let mut ctx = make_ctx(&wb, &layout, ®);
|
||||
ctx.mode = mode;
|
||||
ctx.buffers = &bufs;
|
||||
let km = ks.mode_maps.get(&mode_key).unwrap();
|
||||
let effects = km
|
||||
.dispatch(®, &ctx, KeyCode::Enter, KeyModifiers::NONE)
|
||||
.unwrap_or_else(|| panic!("{mode_key:?}: Enter did not dispatch"));
|
||||
let dbg = effects_debug(&effects);
|
||||
assert!(
|
||||
dbg.contains(expected),
|
||||
"{mode_key:?}: expected {expected}, got: {dbg}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_wizard_has_any_catchall() {
|
||||
let ks = KeymapSet::default_keymaps();
|
||||
|
||||
Reference in New Issue
Block a user