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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ For extra features neotest provides consumers which interact with the state of t

Some consumers will be passive while others can be interacted with.

### Watch Tests

`:h neotest.watch`

Watches files related to tests for changes and re-runs tests

https://user-images.githubusercontent.com/24252670/229367494-6775d7f1-a8fb-461b-bbbd-d6124031293e.mp4

### Output Window

`:h neotest.output`
Expand Down Expand Up @@ -224,6 +232,8 @@ Displays the status of a test/namespace beside the beginning of the definition.

![image](https://user-images.githubusercontent.com/24252670/166143402-b318ef91-c053-4973-b929-5ee97572f2c2.png)

See the help doc for a list of all consumers and their documentation.

## Strategies

Strategies are methods of running tests. They provide the functionality to attach to running processes and so attaching
Expand Down
124 changes: 105 additions & 19 deletions doc/neotest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ neotest *neotest*
Output Consumer.............................................|neotest.output|
Output Panel Consumer.................................|neotest.output_panel|
Run Consumer...................................................|neotest.run|
Watch Consumer...............................................|neotest.watch|
Status Consumer.............................................|neotest.status|
Diagnostic Consumer.....................................|neotest.diagnostic|
Summary Consumer...........................................|neotest.summary|
Expand Down Expand Up @@ -101,7 +102,8 @@ Default values:
skipped = "NeotestSkipped",
target = "NeotestTarget",
test = "NeotestTest",
unknown = "NeotestUnknown"
unknown = "NeotestUnknown",
watching = "NeotestWatching"
},
icons = {
child_indent = "│",
Expand All @@ -116,7 +118,8 @@ Default values:
running = "",
running_animated = { "/", "|", "\\", "-", "/", "|", "\\", "-" },
skipped = "",
unknown = ""
unknown = "",
watching = ""
},
jump = {
enabled = true
Expand Down Expand Up @@ -177,9 +180,18 @@ Default values:
run_marked = "R",
short = "O",
stop = "u",
target = "t"
target = "t",
watch = "w"
},
open = "botright vsplit | vertical resize 50"
},
watch = {
enabled = true,
symbol_queries = {
elixir = <function 1>,
lua = ' ;query\n ;Captures module names in require calls\n (function_call\n name: ((identifier) @function (#eq? @function "require"))\n arguments: (arguments (string) @symbol))\n ',
python = " ;query\n ;Captures imports and modules they're imported from\n (import_from_statement (_ (identifier) @symbol))\n (import_statement (_ (identifier) @symbol))\n "
}
}
}
<
Expand Down Expand Up @@ -219,6 +231,7 @@ Fields~
{quickfix} `(neotest.Config.quickfix)`
{status} `(neotest.Config.status)`
{state} `(neotest.Config.state)`
{watch} `(neotest.Config.watch)`
{diagnostic} `(neotest.Config.diagnostic)`
{projects} `(table<string, neotest.CoreConfig>)` Project specific settings,
keys
Expand All @@ -230,9 +243,8 @@ Fields~
{concurrent} `(integer)` Number of workers to parse files concurrently. 0
automatically assigns number based on CPU. Set to 1 if experiencing lag.
{filter_dir} `(nil)` | fun(name: string, rel_path: string, root: string):
boolean
A function to filter directories when searching for test files. Receives the name,
path relative to project root and project root path
boolean A function to filter directories when searching for test files.
Receives the name, path relative to project root and project root path

*neotest.Config.running*
Fields~
Expand Down Expand Up @@ -293,6 +305,7 @@ for its adapter
adapter
{next_failed} `(string|string[])` Jump to the next failed position
{prev_failed} `(string|string[])` Jump to the previous failed position
{watch} `(string|string[])` Toggle watching for changes

*neotest.Config.output*
Fields~
Expand Down Expand Up @@ -327,6 +340,17 @@ Fields~
{virtual_text} `(boolean)` Display status using virtual text
{signs} `(boolean)` Display status using signs

*neotest.Config.watch*
Fields~
{enabled} `(boolean)`
{symbol_queries} `(table<string, string|fun(root, content: string, path:
string):integer[][]>)` Treesitter queries or functions to capture symbols that
are used for querying the LSP server for defintions to link files. If it is a
function then the return value should be a list of node ranges.
{filter_path?} `(fun(path: string, root: string): boolean)` Returns whether
the watcher should inspect a path for dependencies. Default ignores paths not
under root or common package manager directories.


==============================================================================
neotest.consumers *neotest.consumers*
Expand Down Expand Up @@ -433,6 +457,7 @@ A consumer providing a simple interface to run tests.
Inherits: `neotest.client.RunTreeArgs`

Fields~
{[1]} `(string?)` Position ID to run
{suite} `(boolean)` Run the entire suite instead of a single position

*neotest.run.run()*
Expand Down Expand Up @@ -508,13 +533,6 @@ Parameters~
args then
args[1] should be the position ID.

*neotest.run.adapters()*
`adapters`()

Get the list of all known adapter IDs.
Return~
`(string[])`

*neotest.run.get_last_run()*
`get_last_run`()

Expand All @@ -525,6 +543,65 @@ Return~
`(neotest.run.RunArgs|nil)` args


==============================================================================
neotest.watch *neotest.watch*


Allows watching tests and re-running them whenever related files are
changed. When watching a directory, all files are run in separate processes.
Otherwise the tests are run in the same process (if allowed by the adapter).

Related files are determined through an LSP client through a "best effort"
which means there are cases where a file may not be determined as related
despite it having an effect on a test.

To determine file relationships, a treesitter query is used to find symbols
that are queried for using the `textDocument/definition` LSP request. The
query can be configured through the watch consumer's config. Any captures
named `symbol` will be used. If your language is not present in the default
config, please submit a PR to add support out of the box!

*neotest.watch.watch()*
`watch`({args})

Watch a position and run it whenever related files are changed.
Arguments are the same as the `neotest.run.run`, which allows
for custom runner arguments, env vars, strategy etc. If a position is
already being watched, the existing watcher will be stopped.
Parameters~
{args?} `(neotest.run.RunArgs|string)`

*neotest.watch.toggle()*
`toggle`({args})

Toggle watching a position and run it whenever related files are changed.
Arguments are the same as the `neotest.run.run`, which allows
for custom runner arguments, env vars, strategy etc.

Toggle watching the current file
>vim
lua require("neotest").watch.toggle(vim.fn.expand("%"))
<
Parameters~
{args?} `(neotest.run.RunArgs|string)`

*neotest.watch.stop()*
`stop`({position_id})

Stop watching a position. If no position is provided, all watched positions are stopped.
Parameters~
{position_id} `(string)`

*neotest.watch.is_watching()*
`is_watching`({position_id})

Check if a position is being watched.
Parameters~
{position_id} `(string)`
Return~
`(boolean)`


==============================================================================
neotest.status *neotest.status*

Expand Down Expand Up @@ -575,9 +652,9 @@ Close the summary window

the summary window


>vim
lua require("neotest").summary.toggle()

<

*neotest.summmary.RunMarkedArgs*
Inherits: `neotest.run.RunArgs`
Expand Down Expand Up @@ -1400,17 +1477,26 @@ Return~
Return~
`(neotest.Tree)`

*neotest.types.tree.IterNodesArgs*
Fields~
{continue} `(fun(node: neotest.Tree): boolean)` A predicate for if the given
node's children should be iterated over. Defaults to `true`.

*neotest.Tree:iter_nodes()*
`Tree:iter_nodes`()
`Tree:iter_nodes`({args})

Parameters~
{args?} `(neotest.types.tree.IterNodesArgs)`
Return~
`(fun(): integer,neotest.Tree)`
`(fun():integer,neotest.Tree)`

*neotest.Tree:iter()*
`Tree:iter`()
`Tree:iter`({args})

Parameters~
{args?} `(neotest.types.tree.IterNodesArgs)`
Return~
`(fun(): integer,neotest.Position)`
`(fun():integer,neotest.Position)`

*neotest.Tree:node()*
`Tree:node`({index})
Expand Down
69 changes: 63 additions & 6 deletions lua/neotest/config/init.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local lib = require("neotest.lib")
---@tag neotest.config
---@toc_entry Configuration Options

Expand All @@ -7,7 +8,7 @@ local function define_highlights()
hi default NeotestFailed ctermfg=Red guifg=#F70067
hi default NeotestRunning ctermfg=Yellow guifg=#FFEC63
hi default NeotestSkipped ctermfg=Cyan guifg=#00f1f5
hi default link NeotestTest Normal
hi default link NeotestTest Normal
hi default NeotestNamespace ctermfg=Magenta guifg=#D484FF
hi default NeotestFocused gui=bold,underline cterm=bold,underline
hi default NeotestFile ctermfg=Cyan guifg=#00f1f5
Expand All @@ -18,6 +19,7 @@ local function define_highlights()
hi default NeotestWinSelect ctermfg=Cyan guifg=#00f1f5 gui=bold
hi default NeotestMarked ctermfg=Brown guifg=#F79000 gui=bold
hi default NeotestTarget ctermfg=Red guifg=#F70067
hi default NeotestWatching ctermfg=Yellow guifg=#FFEC63
hi default link NeotestUnknown Normal
]])
end
Expand Down Expand Up @@ -45,17 +47,15 @@ define_highlights()
---@field quickfix neotest.Config.quickfix
---@field status neotest.Config.status
---@field state neotest.Config.state
---@field watch neotest.Config.watch
---@field diagnostic neotest.Config.diagnostic
---@field projects table<string, neotest.CoreConfig> Project specific settings, keys
--- are project root directories (e.g "~/Dev/my_project")

---@class neotest.Config.discovery
---@field enabled boolean
---@field concurrent integer Number of workers to parse files concurrently. 0
--- automatically assigns number based on CPU. Set to 1 if experiencing lag.
---@field filter_dir nil | fun(name: string, rel_path: string, root: string): boolean
--- A function to filter directories when searching for test files. Receives the name,
--- path relative to project root and project root path
---@field concurrent integer Number of workers to parse files concurrently. 0 automatically assigns number based on CPU. Set to 1 if experiencing lag.
---@field filter_dir nil | fun(name: string, rel_path: string, root: string): boolean A function to filter directories when searching for test files. Receives the name, path relative to project root and project root path

---@class neotest.Config.running
---@field concurrent boolean Run tests concurrently when an adapter provides multiple commands to run
Expand Down Expand Up @@ -100,6 +100,7 @@ define_highlights()
---@field clear_target string|string[] Clear the target position for the selected adapter
---@field next_failed string|string[] Jump to the next failed position
---@field prev_failed string|string[] Jump to the previous failed position
---@field watch string|string[] Toggle watching for changes

---@class neotest.Config.output
---@field enabled boolean
Expand All @@ -126,6 +127,11 @@ define_highlights()
---@field virtual_text boolean Display status using virtual text
---@field signs boolean Display status using signs

---@class neotest.Config.watch
---@field enabled boolean
---@field symbol_queries table<string, string|fun(root, content: string, path: string):integer[][]> Treesitter queries or functions to capture symbols that are used for querying the LSP server for defintions to link files. If it is a function then the return value should be a list of node ranges.
---@field filter_path? fun(path: string, root: string): boolean Returns whether the watcher should inspect a path for dependencies. Default ignores paths not under root or common package manager directories.

---@private
---@type neotest.Config
local default_config = {
Expand Down Expand Up @@ -167,6 +173,7 @@ local default_config = {
final_child_prefix = "╰",
child_indent = "│",
final_child_indent = " ",
watching = "",
},
highlights = {
passed = "NeotestPassed",
Expand All @@ -186,6 +193,7 @@ local default_config = {
marked = "NeotestMarked",
target = "NeotestTarget",
unknown = "NeotestUnknown",
watching = "NeotestWatching",
},
floating = {
border = "rounded",
Expand Down Expand Up @@ -224,6 +232,7 @@ local default_config = {
clear_target = "T",
next_failed = "J",
prev_failed = "K",
watch = "w",
},
},
benchmark = {
Expand Down Expand Up @@ -259,6 +268,54 @@ local default_config = {
state = {
enabled = true,
},
watch = {
enabled = true,
symbol_queries = {
python = [[
;query
;Captures imports and modules they're imported from
(import_from_statement (_ (identifier) @symbol))
(import_statement (_ (identifier) @symbol))
]],
lua = [[
;query
;Captures module names in require calls
(function_call
name: ((identifier) @function (#eq? @function "require"))
arguments: (arguments (string) @symbol))
]],
elixir = function(root, content)
local query = lib.treesitter.normalise_query(
"elixir",
[[;; query
(call (identifier) @_func_name
(arguments (alias) @symbol)
(#match? @_func_name "^(alias|require|import|use)")
(#gsub! @symbol ".*%.(.*)" "%1")
)
]]
)
local symbols = {}
for _, match, metadata in query:iter_matches(root, content) do
for id, node in pairs(match) do
local name = query.captures[id]

if name == "symbol" then
local start_row, start_col, end_row, end_col = node:range()
if metadata[id] ~= nil then
local real_symbol_length = string.len(metadata[id]["text"])
start_col = end_col - real_symbol_length
end

symbols[#symbols + 1] = { start_row, start_col, end_row, end_col }
end
end
end
return symbols
end,
},
filter_path = nil,
},
projects = {},
}

Expand Down
Loading