Skip to content

Commit 1c1ff6e

Browse files
authored
Merge pull request #149 from FluxxField/docs/testing-guide
docs: add testing guide and update tests README
2 parents 790dd3a + 1f27122 commit 1c1ff6e

3 files changed

Lines changed: 299 additions & 6 deletions

File tree

docs/Testing.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Testing
2+
3+
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.
4+
5+
---
6+
7+
## Running Tests
8+
9+
```bash
10+
# Full suite (470 tests)
11+
make test
12+
13+
# Single file
14+
make test-file FILE=test_history.lua
15+
```
16+
17+
Both commands run `nvim --headless -u tests/run_tests.lua`. Output shows `o` for pass and `x` for fail.
18+
19+
---
20+
21+
## What's Tested
22+
23+
The suite covers the non-interactive core of the plugin:
24+
25+
| Area | Tests Cover |
26+
|------|-------------|
27+
| **Pipeline engine** | `setup.run()` → collector → extractor → modifier → filter → targets flow |
28+
| **Registry system** | Registration, lookup, dedup for all 7 registry types |
29+
| **Motion registration** | Validation, `register_motion`, composable prefix matching |
30+
| **Filters** | Primitive filters, composed filters, direction metadata |
31+
| **Config** | Field validation, defaults, deprecated field handling |
32+
| **Exit events** | `throw`, `wrap`, `protect`, `safe` flow control |
33+
| **History** | Add/dedup/frecency, serialization, disk persistence, merge-with-disk, global pins |
34+
| **Merge utilities** | `merge_actions`, `merge_filters` chain execution |
35+
| **Module loader** | Resolution, default fallback, infer action handling |
36+
| **Filetype dispatch** | Module swapping, metadata deep-copy |
37+
| **Extractors** | Words, text search coroutine yielding |
38+
| **Collectors** | Lines, patterns, marks, quickfix, diagnostics |
39+
| **Public API** | All registry interfaces, consts, custom registration |
40+
41+
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).
42+
43+
---
44+
45+
## Writing Tests
46+
47+
### File Structure
48+
49+
Create `tests/test_<name>.lua`:
50+
51+
```lua
52+
local MiniTest = require("mini_test")
53+
local expect = MiniTest.expect
54+
local helpers = require("tests.helpers")
55+
56+
local T = MiniTest.new_set({
57+
hooks = {
58+
pre_case = function()
59+
helpers.setup_plugin()
60+
end,
61+
post_case = helpers.cleanup,
62+
},
63+
})
64+
65+
T["group"]["describes the behavior"] = function()
66+
helpers.create_buf({ "hello world" })
67+
helpers.set_cursor(1, 0)
68+
69+
local result = some_function()
70+
expect.equality(result, expected)
71+
end
72+
73+
return T
74+
```
75+
76+
The runner auto-discovers all `tests/test_*.lua` files.
77+
78+
### Key Helpers
79+
80+
```lua
81+
helpers.setup_plugin(overrides?) -- fresh plugin with test config
82+
helpers.create_buf(lines) -- scratch buffer with content
83+
helpers.set_cursor(row, col) -- 1-indexed row, 0-indexed col
84+
helpers.build_ctx() -- minimal SmartMotionContext
85+
helpers.cleanup() -- wipe buffers, clear modules
86+
```
87+
88+
The test config disables timing (`flow_state_timeout_ms = 0`), search UI (`native_search = false`), and background dimming to keep tests deterministic.
89+
90+
### Testing Pipeline Modules
91+
92+
For collectors, extractors, and filters, test the raw `.run()` function directly rather than the wrapped registry version:
93+
94+
```lua
95+
-- Testing a collector
96+
local collector = require("smart-motion.collectors.marks")
97+
local co = collector.run()
98+
local ok, val = coroutine.resume(co, ctx, cfg, ms)
99+
100+
-- Testing a filter
101+
local filter = require("smart-motion.filters.filter_words_after_cursor")
102+
local result = filter.run(ctx, nil, ms, target)
103+
104+
-- Testing an extractor
105+
local extractor = require("smart-motion.extractors.words")
106+
local co = extractor.run(nil, nil, ms, data)
107+
```
108+
109+
### Testing Exit Events
110+
111+
Wrap calls that may throw exit events:
112+
113+
```lua
114+
local exit = require("smart-motion.core.events.exit")
115+
116+
local exit_type = exit.wrap(function()
117+
-- code that may throw AUTO_SELECT, EARLY_EXIT, etc.
118+
end)
119+
120+
expect.equality(exit_type, "auto_select")
121+
```
122+
123+
### Testing Disk I/O
124+
125+
Override filepath functions to use temp directories:
126+
127+
```lua
128+
local history = require("smart-motion.core.history")
129+
local tmpdir = vim.fn.tempname()
130+
vim.fn.mkdir(tmpdir, "p")
131+
132+
local orig = history._get_history_filepath
133+
history._get_history_filepath = function()
134+
return tmpdir .. "/test.json"
135+
end
136+
137+
-- ... test save/load ...
138+
139+
history._get_history_filepath = orig
140+
os.remove(tmpdir .. "/test.json")
141+
```
142+
143+
---
144+
145+
## CI
146+
147+
Tests run on GitHub Actions for Neovim `v0.10.4`, `stable`, and `nightly` on every push to `master`/`dev` and on pull requests.
148+
149+
```yaml
150+
# .github/workflows/test.yml
151+
- uses: rhysd/action-setup-vim@v1
152+
with:
153+
neovim: true
154+
version: ${{ matrix.nvim-version }}
155+
- run: make test
156+
```
157+
158+
---
159+
160+
## Next Steps
161+
162+
> **[Debugging](Debugging.md)**: Logging, inspecting motion state, troubleshooting
163+
164+
> **[Pipeline Architecture](Pipeline-Architecture.md)**: How modules connect
165+
166+
> **[API Reference](API-Reference.md)**: Complete module reference

docs/_Sidebar.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
- [API Reference](API-Reference)
2323
- [Debugging](Debugging)
2424

25+
**Contributing**
26+
- [Testing](Testing)
27+
2528
---
2629

2730
[GitHub Repository](https://github.com/FluxxField/smart-motion.nvim)

tests/README.md

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,132 @@
1-
# SmartMotion Playground / Test Files
1+
# SmartMotion Tests
22

3-
Interactive test files for every SmartMotion preset. Open them in Neovim with SmartMotion loaded and follow the comment instructions.
3+
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.
44

5-
## Quick Start
5+
---
6+
7+
## Automated Test Suite
8+
9+
470 tests across 41 files covering pipeline engine, registries, filters, config validation, history persistence, and more.
10+
11+
### Running Tests
12+
13+
```bash
14+
# Run the full suite
15+
make test
16+
17+
# Run a single test file
18+
make test-file FILE=test_history.lua
19+
```
20+
21+
Both commands launch Neovim in headless mode. Output shows `o` for pass and `x` for fail.
22+
23+
### Test Structure
24+
25+
Every test file follows the same pattern:
26+
27+
```lua
28+
local MiniTest = require("mini_test")
29+
local expect = MiniTest.expect
30+
local helpers = require("tests.helpers")
31+
32+
local T = MiniTest.new_set({
33+
hooks = {
34+
pre_case = function()
35+
helpers.setup_plugin() -- fresh plugin state
36+
end,
37+
post_case = helpers.cleanup, -- wipe buffers, clear package.loaded
38+
},
39+
})
40+
41+
T["group"]["test name"] = function()
42+
helpers.create_buf({ "hello world" })
43+
helpers.set_cursor(1, 0)
44+
45+
-- test logic here
46+
expect.equality(actual, expected)
47+
end
48+
49+
return T
50+
```
51+
52+
### Test Helpers (`tests/helpers.lua`)
53+
54+
| Helper | Description |
55+
|--------|-------------|
56+
| `setup_plugin(overrides?)` | Calls `require("smart-motion").setup()` with a test config that disables timing-dependent features |
57+
| `create_buf(lines)` | Creates a scratch buffer with the given lines and sets it as current |
58+
| `set_cursor(row, col)` | Sets cursor position (1-indexed row, 0-indexed col) |
59+
| `get_cursor()` | Returns current cursor position |
60+
| `build_ctx(overrides?)` | Builds a minimal `SmartMotionContext` from the current window/buffer |
61+
| `get_buf_lines()` | Returns all lines from the current buffer |
62+
| `get_register(reg)` | Returns the contents of a vim register |
63+
| `cleanup()` | Deletes all buffers and clears `package.loaded` for fresh state |
64+
65+
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.
66+
67+
### Test Files
68+
69+
| File | What It Covers |
70+
|------|---------------|
71+
| `test_actions.lua` | Jump, yank, delete, change actions |
72+
| `test_actions_extended.lua` | Action chain execution, operator-pending behavior |
73+
| `test_auto_select.lua` | Single-target auto-selection |
74+
| `test_char_repeat.lua` | `_find_matches`, `_filter_by_direction` for char repeat |
75+
| `test_collectors.lua` | Patterns, quickfix, lines collectors |
76+
| `test_composed_filters.lua` | Composed filter registration, direction metadata, behavioral tests |
77+
| `test_config.lua` | Config validation, defaults, deprecated field handling |
78+
| `test_consts.lua` | Constants integrity |
79+
| `test_context.lua` | Context building and fields |
80+
| `test_diagnostics_collector.lua` | LSP diagnostics collector, severity filtering |
81+
| `test_engine_setup.lua` | `setup.run()`, motion loading, metadata merge, per-mode overrides |
82+
| `test_exit_events.lua` | `throw`, `wrap`, `protect`, `safe` exit event system |
83+
| `test_extractors.lua` | Words and lines extractors |
84+
| `test_filetype_dispatch.lua` | Module swapping, motion_state merge, deep-copy safety |
85+
| `test_filters.lua` | Primitive filters (cursor line, after/before cursor, visible) |
86+
| `test_flow_state.lua` | Flow state timeout and chaining |
87+
| `test_fuzzy.lua` | Fuzzy matching scoring |
88+
| `test_highlight.lua` | Highlight application and cleanup |
89+
| `test_highlight_setup.lua` | Highlight group creation, custom colors |
90+
| `test_hints.lua` | Hint label assignment |
91+
| `test_history.lua` | History add/last/clear, dedup, frecency, serialization, pins |
92+
| `test_history_persistence.lua` | Disk save/load, merge-with-disk, global pins, version compat |
93+
| `test_label_conflict.lua` | Label conflict resolution |
94+
| `test_marks_collector.lua` | Vim marks collector, local-only filtering |
95+
| `test_merge.lua` | `merge_actions`, `merge_filters` chain execution and metadata |
96+
| `test_modifiers.lua` | Sort, proximity, reverse modifiers |
97+
| `test_module_loader.lua` | Module resolution, fallback to default, infer action handling |
98+
| `test_motions.lua` | Motion validation, registration, composable lookup |
99+
| `test_pass_through_visualizer.lua` | Auto-select first target, early exit, metadata |
100+
| `test_pipeline.lua` | Pipeline data flow, coroutine protocol |
101+
| `test_pipeline_integration.lua` | Full `setup.run()``pipeline.run()`, engine loop, count_select |
102+
| `test_plugin_api.lua` | Public API surface for all registries, custom registration |
103+
| `test_presets.lua` | Preset loading, partial enable/disable |
104+
| `test_registry.lua` | Registry CRUD operations, dedup |
105+
| `test_search.lua` | Search pattern building |
106+
| `test_selection_handlers.lua` | Selection handler dispatch |
107+
| `test_state.lua` | Motion state management |
108+
| `test_targets.lua` | Target creation and properties |
109+
| `test_text_search.lua` | Literal match extraction, `\V` pattern, registry variants |
110+
| `test_utils.lua` | Utility functions |
111+
| `test_visual_select.lua` | `_collect_word_targets`, textobject visual selection |
112+
113+
### Writing New Tests
114+
115+
1. Create `tests/test_<name>.lua` following the pattern above
116+
2. Use `helpers.setup_plugin()` in `pre_case` and `helpers.cleanup` in `post_case`
117+
3. For testing pipeline modules (collectors, extractors, filters), work with the raw `.run()` functions rather than the wrapped registry versions
118+
4. Tests that need buffer content should use `helpers.create_buf()` and `helpers.set_cursor()`
119+
5. For testing exit events, wrap calls with `require("smart-motion.core.events.exit").wrap(fn)` to catch thrown exits
120+
121+
The test runner (`tests/run_tests.lua`) automatically discovers all `tests/test_*.lua` files.
122+
123+
---
124+
125+
## Interactive Playground Files
126+
127+
Manual test files for trying SmartMotion presets interactively. Open them in Neovim with SmartMotion loaded and follow the comment instructions.
128+
129+
### Quick Start
6130

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

15-
## Files
139+
### Files
16140

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

26-
## Multi-Window Testing
150+
### Multi-Window Testing
27151

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

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

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

36-
## Tips
160+
### Tips
37161

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

0 commit comments

Comments
 (0)