From 1f2712272305d457303b83316810444d420d4b62 Mon Sep 17 00:00:00 2001 From: Keenan Johns Date: Mon, 9 Mar 2026 15:59:03 -0500 Subject: [PATCH] docs: add testing guide and update tests README - Update tests/README.md to document both automated suite and playground files - Add docs/Testing.md wiki page covering running tests, writing tests, and CI - Add Testing link to wiki sidebar under new Contributing section --- docs/Testing.md | 166 +++++++++++++++++++++++++++++++++++++++++++++++ docs/_Sidebar.md | 3 + tests/README.md | 136 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 299 insertions(+), 6 deletions(-) create mode 100644 docs/Testing.md diff --git a/docs/Testing.md b/docs/Testing.md new file mode 100644 index 0000000..b06b443 --- /dev/null +++ b/docs/Testing.md @@ -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_.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 diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index 4ff23de..67ddc2a 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -22,6 +22,9 @@ - [API Reference](API-Reference) - [Debugging](Debugging) +**Contributing** +- [Testing](Testing) + --- [GitHub Repository](https://github.com/FluxxField/smart-motion.nvim) diff --git a/tests/README.md b/tests/README.md index 0ec3a48..eb5319d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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_.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 @@ -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 | |------|----------------| @@ -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: @@ -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