Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions docs/Testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Testing

SmartMotion uses [mini.test](https://github.com/echasnovski/mini.test) for automated testing. The test suite runs headlessly and requires no extra dependencies beyond Neovim.

---

## Running Tests

```bash
# Full suite (470 tests)
make test

# Single file
make test-file FILE=test_history.lua
```

Both commands run `nvim --headless -u tests/run_tests.lua`. Output shows `o` for pass and `x` for fail.

---

## What's Tested

The suite covers the non-interactive core of the plugin:

| Area | Tests Cover |
|------|-------------|
| **Pipeline engine** | `setup.run()` → collector → extractor → modifier → filter → targets flow |
| **Registry system** | Registration, lookup, dedup for all 7 registry types |
| **Motion registration** | Validation, `register_motion`, composable prefix matching |
| **Filters** | Primitive filters, composed filters, direction metadata |
| **Config** | Field validation, defaults, deprecated field handling |
| **Exit events** | `throw`, `wrap`, `protect`, `safe` flow control |
| **History** | Add/dedup/frecency, serialization, disk persistence, merge-with-disk, global pins |
| **Merge utilities** | `merge_actions`, `merge_filters` chain execution |
| **Module loader** | Resolution, default fallback, infer action handling |
| **Filetype dispatch** | Module swapping, metadata deep-copy |
| **Extractors** | Words, text search coroutine yielding |
| **Collectors** | Lines, patterns, marks, quickfix, diagnostics |
| **Public API** | All registry interfaces, consts, custom registration |

Interactive functions (visualizer label selection, `getchar()`-based input, operator inference) are tested manually using the [playground files](https://github.com/FluxxField/smart-motion.nvim/tree/dev/tests#interactive-playground-files).

---

## Writing Tests

### File Structure

Create `tests/test_<name>.lua`:

```lua
local MiniTest = require("mini_test")
local expect = MiniTest.expect
local helpers = require("tests.helpers")

local T = MiniTest.new_set({
hooks = {
pre_case = function()
helpers.setup_plugin()
end,
post_case = helpers.cleanup,
},
})

T["group"]["describes the behavior"] = function()
helpers.create_buf({ "hello world" })
helpers.set_cursor(1, 0)

local result = some_function()
expect.equality(result, expected)
end

return T
```

The runner auto-discovers all `tests/test_*.lua` files.

### Key Helpers

```lua
helpers.setup_plugin(overrides?) -- fresh plugin with test config
helpers.create_buf(lines) -- scratch buffer with content
helpers.set_cursor(row, col) -- 1-indexed row, 0-indexed col
helpers.build_ctx() -- minimal SmartMotionContext
helpers.cleanup() -- wipe buffers, clear modules
```

The test config disables timing (`flow_state_timeout_ms = 0`), search UI (`native_search = false`), and background dimming to keep tests deterministic.

### Testing Pipeline Modules

For collectors, extractors, and filters, test the raw `.run()` function directly rather than the wrapped registry version:

```lua
-- Testing a collector
local collector = require("smart-motion.collectors.marks")
local co = collector.run()
local ok, val = coroutine.resume(co, ctx, cfg, ms)

-- Testing a filter
local filter = require("smart-motion.filters.filter_words_after_cursor")
local result = filter.run(ctx, nil, ms, target)

-- Testing an extractor
local extractor = require("smart-motion.extractors.words")
local co = extractor.run(nil, nil, ms, data)
```

### Testing Exit Events

Wrap calls that may throw exit events:

```lua
local exit = require("smart-motion.core.events.exit")

local exit_type = exit.wrap(function()
-- code that may throw AUTO_SELECT, EARLY_EXIT, etc.
end)

expect.equality(exit_type, "auto_select")
```

### Testing Disk I/O

Override filepath functions to use temp directories:

```lua
local history = require("smart-motion.core.history")
local tmpdir = vim.fn.tempname()
vim.fn.mkdir(tmpdir, "p")

local orig = history._get_history_filepath
history._get_history_filepath = function()
return tmpdir .. "/test.json"
end

-- ... test save/load ...

history._get_history_filepath = orig
os.remove(tmpdir .. "/test.json")
```

---

## CI

Tests run on GitHub Actions for Neovim `v0.10.4`, `stable`, and `nightly` on every push to `master`/`dev` and on pull requests.

```yaml
# .github/workflows/test.yml
- uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.nvim-version }}
- run: make test
```

---

## Next Steps

> **[Debugging](Debugging.md)**: Logging, inspecting motion state, troubleshooting

> **[Pipeline Architecture](Pipeline-Architecture.md)**: How modules connect

> **[API Reference](API-Reference.md)**: Complete module reference
3 changes: 3 additions & 0 deletions docs/_Sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
- [API Reference](API-Reference)
- [Debugging](Debugging)

**Contributing**
- [Testing](Testing)

---

[GitHub Repository](https://github.com/FluxxField/smart-motion.nvim)
136 changes: 130 additions & 6 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,132 @@
# SmartMotion Playground / Test Files
# SmartMotion Tests

Interactive test files for every SmartMotion preset. Open them in Neovim with SmartMotion loaded and follow the comment instructions.
SmartMotion has two kinds of tests: an **automated test suite** run headlessly via [mini.test](https://github.com/echasnovski/mini.test), and **interactive playground files** for manual testing in Neovim.

## Quick Start
---

## Automated Test Suite

470 tests across 41 files covering pipeline engine, registries, filters, config validation, history persistence, and more.

### Running Tests

```bash
# Run the full suite
make test

# Run a single test file
make test-file FILE=test_history.lua
```

Both commands launch Neovim in headless mode. Output shows `o` for pass and `x` for fail.

### Test Structure

Every test file follows the same pattern:

```lua
local MiniTest = require("mini_test")
local expect = MiniTest.expect
local helpers = require("tests.helpers")

local T = MiniTest.new_set({
hooks = {
pre_case = function()
helpers.setup_plugin() -- fresh plugin state
end,
post_case = helpers.cleanup, -- wipe buffers, clear package.loaded
},
})

T["group"]["test name"] = function()
helpers.create_buf({ "hello world" })
helpers.set_cursor(1, 0)

-- test logic here
expect.equality(actual, expected)
end

return T
```

### Test Helpers (`tests/helpers.lua`)

| Helper | Description |
|--------|-------------|
| `setup_plugin(overrides?)` | Calls `require("smart-motion").setup()` with a test config that disables timing-dependent features |
| `create_buf(lines)` | Creates a scratch buffer with the given lines and sets it as current |
| `set_cursor(row, col)` | Sets cursor position (1-indexed row, 0-indexed col) |
| `get_cursor()` | Returns current cursor position |
| `build_ctx(overrides?)` | Builds a minimal `SmartMotionContext` from the current window/buffer |
| `get_buf_lines()` | Returns all lines from the current buffer |
| `get_register(reg)` | Returns the contents of a vim register |
| `cleanup()` | Deletes all buffers and clears `package.loaded` for fresh state |

The default test config (`helpers.test_config`) sets `flow_state_timeout_ms = 0`, `native_search = false`, `dim_background = false`, and `presets = {}` to keep tests deterministic.

### Test Files

| File | What It Covers |
|------|---------------|
| `test_actions.lua` | Jump, yank, delete, change actions |
| `test_actions_extended.lua` | Action chain execution, operator-pending behavior |
| `test_auto_select.lua` | Single-target auto-selection |
| `test_char_repeat.lua` | `_find_matches`, `_filter_by_direction` for char repeat |
| `test_collectors.lua` | Patterns, quickfix, lines collectors |
| `test_composed_filters.lua` | Composed filter registration, direction metadata, behavioral tests |
| `test_config.lua` | Config validation, defaults, deprecated field handling |
| `test_consts.lua` | Constants integrity |
| `test_context.lua` | Context building and fields |
| `test_diagnostics_collector.lua` | LSP diagnostics collector, severity filtering |
| `test_engine_setup.lua` | `setup.run()`, motion loading, metadata merge, per-mode overrides |
| `test_exit_events.lua` | `throw`, `wrap`, `protect`, `safe` exit event system |
| `test_extractors.lua` | Words and lines extractors |
| `test_filetype_dispatch.lua` | Module swapping, motion_state merge, deep-copy safety |
| `test_filters.lua` | Primitive filters (cursor line, after/before cursor, visible) |
| `test_flow_state.lua` | Flow state timeout and chaining |
| `test_fuzzy.lua` | Fuzzy matching scoring |
| `test_highlight.lua` | Highlight application and cleanup |
| `test_highlight_setup.lua` | Highlight group creation, custom colors |
| `test_hints.lua` | Hint label assignment |
| `test_history.lua` | History add/last/clear, dedup, frecency, serialization, pins |
| `test_history_persistence.lua` | Disk save/load, merge-with-disk, global pins, version compat |
| `test_label_conflict.lua` | Label conflict resolution |
| `test_marks_collector.lua` | Vim marks collector, local-only filtering |
| `test_merge.lua` | `merge_actions`, `merge_filters` chain execution and metadata |
| `test_modifiers.lua` | Sort, proximity, reverse modifiers |
| `test_module_loader.lua` | Module resolution, fallback to default, infer action handling |
| `test_motions.lua` | Motion validation, registration, composable lookup |
| `test_pass_through_visualizer.lua` | Auto-select first target, early exit, metadata |
| `test_pipeline.lua` | Pipeline data flow, coroutine protocol |
| `test_pipeline_integration.lua` | Full `setup.run()` → `pipeline.run()`, engine loop, count_select |
| `test_plugin_api.lua` | Public API surface for all registries, custom registration |
| `test_presets.lua` | Preset loading, partial enable/disable |
| `test_registry.lua` | Registry CRUD operations, dedup |
| `test_search.lua` | Search pattern building |
| `test_selection_handlers.lua` | Selection handler dispatch |
| `test_state.lua` | Motion state management |
| `test_targets.lua` | Target creation and properties |
| `test_text_search.lua` | Literal match extraction, `\V` pattern, registry variants |
| `test_utils.lua` | Utility functions |
| `test_visual_select.lua` | `_collect_word_targets`, textobject visual selection |

### Writing New Tests

1. Create `tests/test_<name>.lua` following the pattern above
2. Use `helpers.setup_plugin()` in `pre_case` and `helpers.cleanup` in `post_case`
3. For testing pipeline modules (collectors, extractors, filters), work with the raw `.run()` functions rather than the wrapped registry versions
4. Tests that need buffer content should use `helpers.create_buf()` and `helpers.set_cursor()`
5. For testing exit events, wrap calls with `require("smart-motion.core.events.exit").wrap(fn)` to catch thrown exits

The test runner (`tests/run_tests.lua`) automatically discovers all `tests/test_*.lua` files.

---

## Interactive Playground Files

Manual test files for trying SmartMotion presets interactively. Open them in Neovim with SmartMotion loaded and follow the comment instructions.

### Quick Start

```vim
" Open a test file
Expand All @@ -12,7 +136,7 @@ Interactive test files for every SmartMotion preset. Open them in Neovim with Sm
:vsplit tests/search.lua
```

## Files
### Files

| File | Presets Covered |
|------|----------------|
Expand All @@ -23,7 +147,7 @@ Interactive test files for every SmartMotion preset. Open them in Neovim with Sm
| `diagnostics.lua` | `]d`, `[d`, `]e`, `[e` (requires LSP) |
| `misc.lua` | `.` (repeat), `gmd`, `gmy` |

## Multi-Window Testing
### Multi-Window Testing

SmartMotion labels appear across all visible windows for search, treesitter, and diagnostic motions. To test:

Expand All @@ -33,7 +157,7 @@ SmartMotion labels appear across all visible windows for search, treesitter, and

Word and line motions (`w`, `b`, `j`, `k`) stay in the current window.

## Tips
### Tips

- Files can be freely edited during testing — use `:e!` to reload the original
- Press `ESC` at any label prompt to cancel cleanly
Expand Down
Loading