Compare commits
20 Commits
dcb4320040
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
456a157549
|
|||
|
8c598002e4
|
|||
|
bd6930adc0
|
|||
|
24bd89a7be
|
|||
|
fd3952bedf
|
|||
|
238d5f9634
|
|||
|
118f84c31e
|
|||
|
924584d8b3
|
|||
|
5d0b4e9dd6
|
|||
|
c538a32307
|
|||
|
e9a7d2029b
|
|||
|
4c2d585f2d
|
|||
|
e315a8e8f2
|
|||
|
1d9b682a58
|
|||
|
6505a91cce
|
|||
|
f5fc9822ce
|
|||
|
58f0edc14b
|
|||
|
77a7ebab4d
|
|||
|
6ce8530cf7
|
|||
|
15bc792449
|
41
.gitea/workflows/tests.yml
Normal file
41
.gitea/workflows/tests.yml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Neovim AppImage
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
arch="$(uname -m)"
|
||||||
|
if [ "${arch}" = "aarch64" ]; then
|
||||||
|
appimage="nvim-linux-arm64.appimage"
|
||||||
|
else
|
||||||
|
appimage="nvim-linux-x86_64.appimage"
|
||||||
|
fi
|
||||||
|
url="https://github.com/neovim/neovim/releases/download/stable/${appimage}"
|
||||||
|
curl -L "${url}" -o "${appimage}"
|
||||||
|
chmod +x "${appimage}"
|
||||||
|
./${appimage} --appimage-extract
|
||||||
|
sudo install -m 0755 ./squashfs-root/usr/bin/nvim /usr/local/bin/nvim
|
||||||
|
|
||||||
|
- name: Neovim Version
|
||||||
|
run: nvim --version
|
||||||
|
|
||||||
|
- name: Install plenary.nvim
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
target_dir="${HOME}/.local/share/nvim/site/pack/vendor/start/plenary.nvim"
|
||||||
|
mkdir -p "$(dirname "${target_dir}")"
|
||||||
|
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim "${target_dir}"
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: bash run_test.sh
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
output
|
output
|
||||||
.nvimlog
|
.nvimlog
|
||||||
|
.idea
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
- **Keine stillen Änderungen**:
|
- **Keine stillen Änderungen**:
|
||||||
- Bestehende Features dürfen nicht unbemerkt geändert oder ersetzt werden.
|
- Bestehende Features dürfen nicht unbemerkt geändert oder ersetzt werden.
|
||||||
- Notwendige Anpassungen zur Koexistenz mehrerer Features müssen klar erkennbar sein.
|
- Notwendige Anpassungen zur Koexistenz mehrerer Features müssen klar erkennbar sein.
|
||||||
|
- **Hilfe-Ansicht aktuell halten**:
|
||||||
|
- Änderungen und/oder Erweiterungen müssen **immer** die `:help`-Dokumentation, die README.md und die `quick-help`-Ansicht (via `?`) automatisch aktualisieren.
|
||||||
|
- Sprache der Hilfe ist wie die README.md immer **englisch**.
|
||||||
|
|
||||||
## Projektziel
|
## Projektziel
|
||||||
- Neovim Plugin: **test-samurai**
|
- Neovim Plugin: **test-samurai**
|
||||||
|
|||||||
133
README.md
Normal file
133
README.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# test-samurai.nvim
|
||||||
|
|
||||||
|
A Neovim plugin to run tests across multiple languages and frameworks with a unified UX and an extensible runner architecture.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Neovim >= 0.11.4
|
||||||
|
- Lua
|
||||||
|
- Test runners are provided as separate Lua modules
|
||||||
|
|
||||||
|
## Installation (Lazy.nvim)
|
||||||
|
|
||||||
|
Use the GitHub repository. The example below shows how to add the Go runner as a dependency and configure it in `setup()`:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
"m13r/test-samurai.nvim",
|
||||||
|
dependencies = {
|
||||||
|
"m13r/test-samurai-go-runner",
|
||||||
|
-- furthur samurai runners
|
||||||
|
},
|
||||||
|
config = function()
|
||||||
|
require("test-samurai").setup({
|
||||||
|
runner_modules = {
|
||||||
|
"test-samurai-go-runner",
|
||||||
|
-- furthur samurai runners
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Runner modules (required)
|
||||||
|
|
||||||
|
test-samurai does not ship with any built-in runners. You must explicitly configure the runners you want to use:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
require("test-samurai").setup({
|
||||||
|
runner_modules = {
|
||||||
|
"my-runners.go",
|
||||||
|
"my-runners.js",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
If no runner matches the current test file, test-samurai will show:
|
||||||
|
|
||||||
|
```
|
||||||
|
[test-samurai] no runner installed for this kind of test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands and keymaps
|
||||||
|
|
||||||
|
- `TSamNearest` -> `<leader>tn`
|
||||||
|
- `TSamFile` -> `<leader>tf`
|
||||||
|
- `TSamAll` -> `<leader>ta`
|
||||||
|
- `TSamLast` -> `<leader>tl`
|
||||||
|
- `TSamFailedOnly` -> `<leader>te`
|
||||||
|
- `TSamShowOutput` -> `<leader>to`
|
||||||
|
- Help: `:help test-samurai`
|
||||||
|
|
||||||
|
Additional keymaps:
|
||||||
|
|
||||||
|
- Listing navigation:
|
||||||
|
- `<leader>fn` -> [F]ind [N]ext failed test in listing (wraps to the first, opens Detail-Float, works in Detail-Float)
|
||||||
|
- `<leader>fp` -> [F]ind [P]revious failed test in listing (wraps to the last, opens Detail-Float, works in Detail-Float)
|
||||||
|
- `<leader>ff` -> [F]ind [F]irst list entry (opens Detail-Float, works in Detail-Float)
|
||||||
|
- `<leader>o` -> jump to the test location
|
||||||
|
- `<leader>qn` -> close the testing floats and jump to the first quickfix entry
|
||||||
|
- Listing filters:
|
||||||
|
- `<leader>sf` -> filter the listing to `[ FAIL ] - ...` entries
|
||||||
|
- `<leader>ss` -> filter the listing to `[ SKIP ] - ...` entries
|
||||||
|
- `<leader>sa` -> clear the listing filter and show all entries
|
||||||
|
- Listing actions:
|
||||||
|
- `<leader>tt` -> run the test under the cursor in the listing
|
||||||
|
- `<leader>cb` -> breaks test-command onto multiple lines (clears search highlight)
|
||||||
|
- `<leader>cj` -> joins test-command onto single line
|
||||||
|
- `?` -> show help with TSam commands and standard keymaps in the Detail-Float
|
||||||
|
|
||||||
|
Before running any test command, test-samurai runs `:wall` to save all buffers.
|
||||||
|
|
||||||
|
## Output UI
|
||||||
|
|
||||||
|
- Output is shown in a floating container called **Testing-Float**.
|
||||||
|
- The **Test-Listing-Float** is the left subwindow and shows the test result list.
|
||||||
|
- The **Detail-Float** is the right subwindow and shows detailed output for a selected test.
|
||||||
|
- After `TSamNearest`, `TSamFile`, `TSamAll`, `TSamFailedOnly`, etc., the UI opens in listing mode (only **Test-Listing-Float** visible).
|
||||||
|
- Press `<cr>` on a `[ FAIL ] ...` line in the listing to open/update the **Detail-Float** as a 20/80 split (left 20% listing, right 80% detail).
|
||||||
|
- ANSI color translation is only applied in the **Detail-Float**; the **Test-Listing-Float** shows raw text without ANSI translation.
|
||||||
|
- `<esc><esc>` hides the floating window and restores the cursor position; `TSamShowOutput` reopens it.
|
||||||
|
- If no output is captured for a test, the **Detail-Float** shows `No output captured`.
|
||||||
|
- The active listing entry is highlighted while the **Detail-Float** is visible.
|
||||||
|
- Summary lines (`TOTAL`/`DURATION`) are appended in the listing output, including `TSamLast`.
|
||||||
|
|
||||||
|
## Runner architecture
|
||||||
|
|
||||||
|
Runners are standalone Lua modules. All runner modules are expected to implement the full interface so every command and keymap works.
|
||||||
|
All functions are required (including the previously optional ones) and listing output must be streamed.
|
||||||
|
Required functions:
|
||||||
|
|
||||||
|
- `is_test_file`
|
||||||
|
- `find_nearest`
|
||||||
|
- `build_command`
|
||||||
|
- `build_file_command`
|
||||||
|
- `build_all_command`
|
||||||
|
- `build_failed_command`
|
||||||
|
- `parse_results`
|
||||||
|
- `output_parser` (must stream listing output via `on_line`)
|
||||||
|
- `parse_test_output`
|
||||||
|
- `collect_failed_locations`
|
||||||
|
|
||||||
|
No runner for your environment exists? No problem: use `runners-agents.md` to guide an AI-assisted runner implementation tailored to your stack.
|
||||||
|
|
||||||
|
## Known runners
|
||||||
|
|
||||||
|
- [`m13r/test-samurai-go-runner`](https://gitea.mschirmer.com/m13r/test-samurai-go-runner)
|
||||||
|
- [`m13r/test-samurai-jest-runner`](https://gitea.mschirmer.com/m13r/test-samurai-jest-runner)
|
||||||
|
- [`m13r/test-samurai-mocha-runner`](https://gitea.mschirmer.com/m13r/test-samurai-mocha-runner)
|
||||||
|
- [`m13r/test-samurai-vitest-runner`](https://gitea.mschirmer.com/m13r/test-samurai-vitest-runner)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Runner development guidelines, including required data formats for keymaps, tests (`run_test.sh`), Gitea CI (Neovim AppImage on ARM runners), and framework-agnostic best practices (naming conventions, TSamNearest priority, reporter payloads, failed-only behavior), are documented in `runner-agents.md`.
|
||||||
|
|
||||||
|
Tests are written with `plenary.nvim` / `busted`. Mocks and stubs are allowed.
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash run_test.sh
|
||||||
|
```
|
||||||
9
doc/tags
Normal file
9
doc/tags
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
test-samurai test-samurai.txt /*test-samurai*
|
||||||
|
test-samurai-commands test-samurai.txt /*test-samurai-commands*
|
||||||
|
test-samurai-default-keys test-samurai.txt /*test-samurai-default-keys*
|
||||||
|
test-samurai-quickhelp test-samurai.txt /*test-samurai-quickhelp*
|
||||||
|
test-samurai-req test-samurai.txt /*test-samurai-req*
|
||||||
|
test-samurai-runners test-samurai.txt /*test-samurai-runners*
|
||||||
|
test-samurai-setup test-samurai.txt /*test-samurai-setup*
|
||||||
|
test-samurai-ui test-samurai.txt /*test-samurai-ui*
|
||||||
|
test-samurai.txt test-samurai.txt /*test-samurai.txt*
|
||||||
102
doc/test-samurai.txt
Normal file
102
doc/test-samurai.txt
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
*test-samurai.txt* Run tests with a unified UX
|
||||||
|
|
||||||
|
INTRODUCTION *test-samurai*
|
||||||
|
|
||||||
|
Test Samurai provides a unified UX to run tests across languages
|
||||||
|
and frameworks, backed by an extensible runner architecture.
|
||||||
|
|
||||||
|
|
||||||
|
REQUIREMENTS *test-samurai-req*
|
||||||
|
|
||||||
|
Neovim 0.11.4 or later is required.
|
||||||
|
|
||||||
|
|
||||||
|
SETUP *test-samurai-setup*
|
||||||
|
|
||||||
|
This plugin ships without built-in runners. Configure runner modules
|
||||||
|
in your setup so test commands and keymaps work properly:
|
||||||
|
|
||||||
|
>
|
||||||
|
require("test-samurai").setup({
|
||||||
|
runner_modules = {
|
||||||
|
"my-runners.go",
|
||||||
|
"my-runners.js",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
<
|
||||||
|
|
||||||
|
|
||||||
|
COMMANDS *test-samurai-commands*
|
||||||
|
|
||||||
|
:TSamNearest Run the nearest test.
|
||||||
|
:TSamFile Run all tests in the current file.
|
||||||
|
:TSamAll Run all tests in the project.
|
||||||
|
:TSamLast Re-run the last test command.
|
||||||
|
:TSamFailedOnly Re-run only previously failed tests.
|
||||||
|
:TSamShowOutput Reopen the Testing-Float output window.
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT KEYMAPS *test-samurai-default-keys*
|
||||||
|
|
||||||
|
TSamNearest <leader>tn
|
||||||
|
TSamFile <leader>tf
|
||||||
|
TSamAll <leader>ta
|
||||||
|
TSamLast <leader>tl
|
||||||
|
TSamFailedOnly <leader>te
|
||||||
|
TSamShowOutput <leader>to
|
||||||
|
|
||||||
|
|
||||||
|
QUICK-HELP & FLOATS *test-samurai-quickhelp*
|
||||||
|
|
||||||
|
In the Testing-Float, press ? to open the quick-help in the Detail-Float.
|
||||||
|
|
||||||
|
Additional keymaps:
|
||||||
|
Listing navigation:
|
||||||
|
<leader>fn [F]ind [N]ext failed test in listing (opens Detail-Float; works in Detail-Float)
|
||||||
|
<leader>fp [F]ind [P]revious failed test in listing (opens Detail-Float; works in Detail-Float)
|
||||||
|
<leader>ff [F]ind [F]irst list entry (opens Detail-Float; works in Detail-Float)
|
||||||
|
<leader>o Jump to test location
|
||||||
|
<leader>qn Close floats + jump to the first quickfix entry
|
||||||
|
Listing filters:
|
||||||
|
<leader>sf Filter listing to [ FAIL ] only
|
||||||
|
<leader>ss Filter listing to [ SKIP ] only
|
||||||
|
<leader>sa Show all listing entries (clear filter)
|
||||||
|
Listing actions:
|
||||||
|
<leader>tt Run the test under the cursor in the listing
|
||||||
|
<leader>cb breaks test-command onto multiple lines (clears search highlight)
|
||||||
|
<leader>cj joins test-command onto single line
|
||||||
|
Testing-Float:
|
||||||
|
<leader>z Toggle Detail-Float full width
|
||||||
|
<C-l> Focus Detail-Float (press l again for full)
|
||||||
|
<C-h> Focus Test-Listing-Float
|
||||||
|
<esc><esc> Close Testing-Float and restore cursor
|
||||||
|
<C-c> Close Detail-Float (when focused)
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Active listing entry is highlighted while the Detail-Float is visible.
|
||||||
|
Buffers are saved via :wall before every test run.
|
||||||
|
|
||||||
|
|
||||||
|
OUTPUT UI *test-samurai-ui*
|
||||||
|
|
||||||
|
Testing-Float: container floating window for output.
|
||||||
|
Test-Listing-Float: left subwindow listing test results.
|
||||||
|
Detail-Float: right subwindow showing detailed output for a test.
|
||||||
|
|
||||||
|
After running a test command, the UI opens in listing mode (only the
|
||||||
|
Test-Listing-Float is visible). Press <cr> on a [ FAIL ] entry to open
|
||||||
|
the Detail-Float with a 20/80 split. ANSI colors are translated only
|
||||||
|
inside the Detail-Float. If no output is captured for a test, the
|
||||||
|
Detail-Float shows "No output captured".
|
||||||
|
|
||||||
|
Summary lines (TOTAL/DURATION) are rendered in the listing output,
|
||||||
|
including after :TSamLast.
|
||||||
|
|
||||||
|
|
||||||
|
RUNNER ARCHITECTURE *test-samurai-runners*
|
||||||
|
|
||||||
|
Runners are standalone Lua modules that implement the full interface.
|
||||||
|
See README.md for the required functions and runner guidelines.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
vim:ft=help
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local defaults = {
|
local defaults = {
|
||||||
runner_modules = {
|
runner_modules = {},
|
||||||
"test-samurai.runners.go",
|
|
||||||
"test-samurai.runners.js-jest",
|
|
||||||
"test-samurai.runners.js-mocha",
|
|
||||||
"test-samurai.runners.js-vitest",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local current = vim.deepcopy(defaults)
|
local current = vim.deepcopy(defaults)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,502 +0,0 @@
|
|||||||
local util = require("test-samurai.util")
|
|
||||||
|
|
||||||
local runner = {
|
|
||||||
name = "go",
|
|
||||||
}
|
|
||||||
|
|
||||||
local function find_block_end(lines, start_idx)
|
|
||||||
local depth = 0
|
|
||||||
local started = false
|
|
||||||
for i = start_idx, #lines do
|
|
||||||
local line = lines[i]
|
|
||||||
for j = 1, #line do
|
|
||||||
local ch = line:sub(j, j)
|
|
||||||
if ch == "{" then
|
|
||||||
depth = depth + 1
|
|
||||||
started = true
|
|
||||||
elseif ch == "}" then
|
|
||||||
if started then
|
|
||||||
depth = depth - 1
|
|
||||||
if depth == 0 then
|
|
||||||
return i - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return #lines - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local function find_test_functions(lines)
|
|
||||||
local funcs = {}
|
|
||||||
for i, line in ipairs(lines) do
|
|
||||||
local name = line:match("^%s*func%s+([%w_]+)%s*%(")
|
|
||||||
if not name then
|
|
||||||
name = line:match("^%s*func%s+%([^)]-%)%s+([%w_]+)%s*%(")
|
|
||||||
end
|
|
||||||
if name and line:find("%*testing%.T") then
|
|
||||||
local start_0 = i - 1
|
|
||||||
local end_0 = find_block_end(lines, i)
|
|
||||||
table.insert(funcs, {
|
|
||||||
name = name,
|
|
||||||
start = start_0,
|
|
||||||
["end"] = end_0,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return funcs
|
|
||||||
end
|
|
||||||
|
|
||||||
local function find_t_runs(lines, func)
|
|
||||||
local subtests = {}
|
|
||||||
for i = func.start + 1, func["end"] do
|
|
||||||
local line = lines[i + 1]
|
|
||||||
if line then
|
|
||||||
local name = line:match("t%.Run%(%s*['\"]([^'\"]+)['\"]")
|
|
||||||
if name then
|
|
||||||
local start_idx = i + 1
|
|
||||||
local end_0 = find_block_end(lines, start_idx)
|
|
||||||
table.insert(subtests, {
|
|
||||||
name = name,
|
|
||||||
start = start_idx - 1,
|
|
||||||
["end"] = end_0,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return subtests
|
|
||||||
end
|
|
||||||
|
|
||||||
local function escape_go_regex(s)
|
|
||||||
s = s or ""
|
|
||||||
return (s:gsub("([\\.^$|()%%[%]{}*+?%-])", "\\\\%1"))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function build_run_pattern(spec)
|
|
||||||
local name = spec.test_path or ""
|
|
||||||
local escaped = escape_go_regex(name)
|
|
||||||
if spec.scope == "function" then
|
|
||||||
return "^" .. escaped .. "($|/)"
|
|
||||||
else
|
|
||||||
return "^" .. escaped .. "$"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function build_pkg_arg(spec)
|
|
||||||
local file = spec.file
|
|
||||||
local cwd = spec.cwd
|
|
||||||
if not file or not cwd or file == "" or cwd == "" then
|
|
||||||
return "./..."
|
|
||||||
end
|
|
||||||
|
|
||||||
local dir = vim.fs.dirname(file)
|
|
||||||
if dir == cwd then
|
|
||||||
return "./"
|
|
||||||
end
|
|
||||||
|
|
||||||
if file:sub(1, #cwd) ~= cwd then
|
|
||||||
return "./..."
|
|
||||||
end
|
|
||||||
|
|
||||||
local rel = dir:sub(#cwd + 2)
|
|
||||||
if not rel or rel == "" then
|
|
||||||
return "./"
|
|
||||||
end
|
|
||||||
|
|
||||||
return "./" .. rel
|
|
||||||
end
|
|
||||||
|
|
||||||
local function collect_unique(list)
|
|
||||||
local out = {}
|
|
||||||
local seen = {}
|
|
||||||
for _, item in ipairs(list) do
|
|
||||||
if item and item ~= "" and not seen[item] then
|
|
||||||
seen[item] = true
|
|
||||||
table.insert(out, item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.is_test_file(bufnr)
|
|
||||||
local path = util.get_buf_path(bufnr)
|
|
||||||
if not path or path == "" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
return path:sub(-8) == "_test.go"
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.find_nearest(bufnr, row, _col)
|
|
||||||
if not runner.is_test_file(bufnr) then
|
|
||||||
return nil, "not a Go test file"
|
|
||||||
end
|
|
||||||
|
|
||||||
local lines = util.get_buf_lines(bufnr)
|
|
||||||
local funcs = find_test_functions(lines)
|
|
||||||
|
|
||||||
local current
|
|
||||||
for _, f in ipairs(funcs) do
|
|
||||||
if row >= f.start and row <= f["end"] then
|
|
||||||
current = f
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not current then
|
|
||||||
return nil, "cursor not inside a test function"
|
|
||||||
end
|
|
||||||
|
|
||||||
local subtests = find_t_runs(lines, current)
|
|
||||||
local inside_sub
|
|
||||||
for _, sub in ipairs(subtests) do
|
|
||||||
if row >= sub.start and row <= sub["end"] then
|
|
||||||
inside_sub = sub
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local path = util.get_buf_path(bufnr)
|
|
||||||
local root = util.find_root(path, { "go.mod", ".git" })
|
|
||||||
|
|
||||||
if inside_sub then
|
|
||||||
local full = current.name .. "/" .. inside_sub.name
|
|
||||||
return {
|
|
||||||
file = path,
|
|
||||||
cwd = root,
|
|
||||||
test_path = full,
|
|
||||||
scope = "subtest",
|
|
||||||
func = current.name,
|
|
||||||
subtest = inside_sub.name,
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return {
|
|
||||||
file = path,
|
|
||||||
cwd = root,
|
|
||||||
test_path = current.name,
|
|
||||||
scope = "function",
|
|
||||||
func = current.name,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.build_command(spec)
|
|
||||||
local pattern = build_run_pattern(spec)
|
|
||||||
local pkg = build_pkg_arg(spec)
|
|
||||||
local cmd = { "go", "test", "-json", pkg, "-run", pattern }
|
|
||||||
return {
|
|
||||||
cmd = cmd,
|
|
||||||
cwd = spec.cwd,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.build_file_command(bufnr)
|
|
||||||
local path = util.get_buf_path(bufnr)
|
|
||||||
if not path or path == "" then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local root = util.find_root(path, { "go.mod", ".git" })
|
|
||||||
if not root or root == "" then
|
|
||||||
root = vim.loop.cwd()
|
|
||||||
end
|
|
||||||
local spec = { file = path, cwd = root }
|
|
||||||
local pkg = build_pkg_arg(spec)
|
|
||||||
local cmd = { "go", "test", "-json", pkg }
|
|
||||||
local lines = util.get_buf_lines(bufnr)
|
|
||||||
local funcs = find_test_functions(lines)
|
|
||||||
local names = {}
|
|
||||||
for _, fn in ipairs(funcs) do
|
|
||||||
table.insert(names, fn.name)
|
|
||||||
end
|
|
||||||
names = collect_unique(names)
|
|
||||||
if #names > 0 then
|
|
||||||
local pattern_parts = {}
|
|
||||||
for _, name in ipairs(names) do
|
|
||||||
table.insert(pattern_parts, escape_go_regex(name))
|
|
||||||
end
|
|
||||||
local pattern = "^(" .. table.concat(pattern_parts, "|") .. ")$"
|
|
||||||
table.insert(cmd, "-run")
|
|
||||||
table.insert(cmd, pattern)
|
|
||||||
end
|
|
||||||
return {
|
|
||||||
cmd = cmd,
|
|
||||||
cwd = root,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.build_all_command(bufnr)
|
|
||||||
local path = util.get_buf_path(bufnr)
|
|
||||||
local root
|
|
||||||
if path and path ~= "" then
|
|
||||||
root = util.find_root(path, { "go.mod", ".git" })
|
|
||||||
end
|
|
||||||
if not root or root == "" then
|
|
||||||
root = vim.loop.cwd()
|
|
||||||
end
|
|
||||||
local cmd = { "go", "test", "-json", "./..." }
|
|
||||||
return {
|
|
||||||
cmd = cmd,
|
|
||||||
cwd = root,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.parse_results(output)
|
|
||||||
if not output or output == "" then
|
|
||||||
return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } }
|
|
||||||
end
|
|
||||||
local passes = {}
|
|
||||||
local failures = {}
|
|
||||||
local skips = {}
|
|
||||||
local display = { passes = {}, failures = {}, skips = {} }
|
|
||||||
for line in output:gmatch("[^\n]+") do
|
|
||||||
local ok, data = pcall(vim.json.decode, line)
|
|
||||||
if ok and type(data) == "table" then
|
|
||||||
if data.Test and data.Test ~= "" then
|
|
||||||
if data.Action == "pass" then
|
|
||||||
table.insert(passes, data.Test)
|
|
||||||
local short = data.Test:match("([^/]+)$") or data.Test
|
|
||||||
table.insert(display.passes, short)
|
|
||||||
elseif data.Action == "fail" then
|
|
||||||
table.insert(failures, data.Test)
|
|
||||||
local short = data.Test:match("([^/]+)$") or data.Test
|
|
||||||
table.insert(display.failures, short)
|
|
||||||
elseif data.Action == "skip" then
|
|
||||||
table.insert(skips, data.Test)
|
|
||||||
local short = data.Test:match("([^/]+)$") or data.Test
|
|
||||||
table.insert(display.skips, short)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return {
|
|
||||||
passes = collect_unique(passes),
|
|
||||||
failures = collect_unique(failures),
|
|
||||||
skips = collect_unique(skips),
|
|
||||||
display = display,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local function split_output_lines(text)
|
|
||||||
if not text or text == "" then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
local lines = vim.split(text, "\n", { plain = true })
|
|
||||||
if #lines > 0 and lines[#lines] == "" then
|
|
||||||
table.remove(lines, #lines)
|
|
||||||
end
|
|
||||||
return lines
|
|
||||||
end
|
|
||||||
|
|
||||||
local function normalize_go_name(name)
|
|
||||||
if not name or name == "" then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return (name:gsub("%s+", "_"))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function add_location(target, key, file, line, label)
|
|
||||||
if not key or key == "" or not file or file == "" or not line then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local text = label or key
|
|
||||||
if not target[key] then
|
|
||||||
target[key] = {}
|
|
||||||
end
|
|
||||||
table.insert(target[key], {
|
|
||||||
filename = file,
|
|
||||||
lnum = line,
|
|
||||||
col = 1,
|
|
||||||
text = text,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
local function collect_file_locations(file, target)
|
|
||||||
local ok, lines = pcall(vim.fn.readfile, file)
|
|
||||||
if not ok or type(lines) ~= "table" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local funcs = find_test_functions(lines)
|
|
||||||
for _, fn in ipairs(funcs) do
|
|
||||||
add_location(target, fn.name, file, fn.start + 1, fn.name)
|
|
||||||
local normalized = normalize_go_name(fn.name)
|
|
||||||
if normalized and normalized ~= fn.name then
|
|
||||||
add_location(target, normalized, file, fn.start + 1, fn.name)
|
|
||||||
end
|
|
||||||
for _, sub in ipairs(find_t_runs(lines, fn)) do
|
|
||||||
local full = fn.name .. "/" .. sub.name
|
|
||||||
add_location(target, full, file, sub.start + 1, full)
|
|
||||||
local normalized_full = normalize_go_name(full)
|
|
||||||
if normalized_full and normalized_full ~= full then
|
|
||||||
add_location(target, normalized_full, file, sub.start + 1, full)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function collect_go_test_files(root)
|
|
||||||
if not root or root == "" then
|
|
||||||
root = vim.loop.cwd()
|
|
||||||
end
|
|
||||||
local files = vim.fn.globpath(root, "**/*_test.go", false, true)
|
|
||||||
if type(files) ~= "table" then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
return files
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.parse_test_output(output)
|
|
||||||
local out = {}
|
|
||||||
if not output or output == "" then
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
for line in output:gmatch("[^\n]+") do
|
|
||||||
local ok, data = pcall(vim.json.decode, line)
|
|
||||||
if ok and type(data) == "table" and data.Action == "output" and data.Test and data.Output then
|
|
||||||
if not out[data.Test] then
|
|
||||||
out[data.Test] = {}
|
|
||||||
end
|
|
||||||
for _, item in ipairs(split_output_lines(data.Output)) do
|
|
||||||
table.insert(out[data.Test], item)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return out
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.output_parser()
|
|
||||||
local seen_pass = {}
|
|
||||||
local seen_fail = {}
|
|
||||||
local failures = {}
|
|
||||||
local passes = {}
|
|
||||||
local skips = {}
|
|
||||||
local display = { passes = {}, failures = {}, skips = {} }
|
|
||||||
|
|
||||||
return {
|
|
||||||
on_line = function(line, _state)
|
|
||||||
local ok, data = pcall(vim.json.decode, line)
|
|
||||||
if not ok or type(data) ~= "table" then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local name = data.Test
|
|
||||||
if not name or name == "" then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local short = name:match("([^/]+)$") or name
|
|
||||||
if data.Action == "pass" and not seen_pass[name] then
|
|
||||||
seen_pass[name] = true
|
|
||||||
table.insert(passes, name)
|
|
||||||
table.insert(display.passes, short)
|
|
||||||
return {
|
|
||||||
passes = { name },
|
|
||||||
failures = {},
|
|
||||||
skips = {},
|
|
||||||
display = { passes = { short }, failures = {}, skips = {} },
|
|
||||||
failures_all = vim.deepcopy(failures),
|
|
||||||
}
|
|
||||||
elseif data.Action == "fail" and not seen_fail[name] then
|
|
||||||
seen_fail[name] = true
|
|
||||||
table.insert(failures, name)
|
|
||||||
table.insert(display.failures, short)
|
|
||||||
return {
|
|
||||||
passes = {},
|
|
||||||
failures = { name },
|
|
||||||
skips = {},
|
|
||||||
display = { passes = {}, failures = { short }, skips = {} },
|
|
||||||
failures_all = vim.deepcopy(failures),
|
|
||||||
}
|
|
||||||
elseif data.Action == "skip" and not seen_pass[name] then
|
|
||||||
seen_pass[name] = true
|
|
||||||
table.insert(skips, name)
|
|
||||||
table.insert(display.skips, short)
|
|
||||||
return {
|
|
||||||
passes = {},
|
|
||||||
failures = {},
|
|
||||||
skips = { name },
|
|
||||||
display = { passes = {}, failures = {}, skips = { short } },
|
|
||||||
failures_all = vim.deepcopy(failures),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end,
|
|
||||||
on_complete = function(_output, _state)
|
|
||||||
return nil
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.build_failed_command(last_command, failures, _scope_kind)
|
|
||||||
if not last_command or type(last_command.cmd) ~= "table" then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local pattern_parts = {}
|
|
||||||
for _, name in ipairs(failures or {}) do
|
|
||||||
table.insert(pattern_parts, escape_go_regex(name))
|
|
||||||
end
|
|
||||||
if #pattern_parts == 0 then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local pattern = "^(" .. table.concat(pattern_parts, "|") .. ")$"
|
|
||||||
|
|
||||||
local cmd = {}
|
|
||||||
local skip_next = false
|
|
||||||
for _, arg in ipairs(last_command.cmd) do
|
|
||||||
if skip_next then
|
|
||||||
skip_next = false
|
|
||||||
elseif arg == "-run" then
|
|
||||||
skip_next = true
|
|
||||||
else
|
|
||||||
table.insert(cmd, arg)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(cmd, "-run")
|
|
||||||
table.insert(cmd, pattern)
|
|
||||||
|
|
||||||
return {
|
|
||||||
cmd = cmd,
|
|
||||||
cwd = last_command.cwd,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.collect_failed_locations(failures, command, scope_kind)
|
|
||||||
if type(failures) ~= "table" or #failures == 0 then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
local files = {}
|
|
||||||
if scope_kind == "all" then
|
|
||||||
files = collect_go_test_files(command and command.cwd or nil)
|
|
||||||
elseif command and command.file then
|
|
||||||
files = { command.file }
|
|
||||||
end
|
|
||||||
if #files == 0 then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
local locations = {}
|
|
||||||
for _, file in ipairs(files) do
|
|
||||||
collect_file_locations(file, locations)
|
|
||||||
end
|
|
||||||
local items = {}
|
|
||||||
local seen = {}
|
|
||||||
local function add_locations(name, locs)
|
|
||||||
for _, loc in ipairs(locs or {}) do
|
|
||||||
local key = string.format("%s:%d:%s", loc.filename or "", loc.lnum or 0, loc.text or name or "")
|
|
||||||
if not seen[key] then
|
|
||||||
seen[key] = true
|
|
||||||
table.insert(items, loc)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for _, name in ipairs(failures) do
|
|
||||||
local direct = locations[name]
|
|
||||||
if direct then
|
|
||||||
add_locations(name, direct)
|
|
||||||
elseif not name:find("/", 1, true) then
|
|
||||||
for full, locs in pairs(locations) do
|
|
||||||
if full:sub(-#name - 1) == "/" .. name then
|
|
||||||
add_locations(full, locs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return items
|
|
||||||
end
|
|
||||||
|
|
||||||
return runner
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
local js = require("test-samurai.runners.js")
|
|
||||||
|
|
||||||
return js.new({
|
|
||||||
name = "js-jest",
|
|
||||||
framework = "jest",
|
|
||||||
command = { "npx", "jest" },
|
|
||||||
json_args = { "--json", "--verbose" },
|
|
||||||
})
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
local js = require("test-samurai.runners.js")
|
|
||||||
|
|
||||||
return js.new({
|
|
||||||
name = "js-mocha",
|
|
||||||
framework = "mocha",
|
|
||||||
command = { "npx", "mocha" },
|
|
||||||
all_glob = "test/**/*.test.js",
|
|
||||||
json_args = { "--reporter", "json-stream" },
|
|
||||||
})
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
local js = require("test-samurai.runners.js")
|
|
||||||
|
|
||||||
return js.new({
|
|
||||||
name = "js-vitest",
|
|
||||||
framework = "vitest",
|
|
||||||
command = { "npx", "vitest" },
|
|
||||||
json_args = { "--reporter", "tap-flat" },
|
|
||||||
})
|
|
||||||
File diff suppressed because it is too large
Load Diff
247
runner-agents.md
Normal file
247
runner-agents.md
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
# runner-agents.md — test-samurai Runner-API
|
||||||
|
|
||||||
|
Ziel: Diese Datei beschreibt die öffentliche Runner-API, die ein neuer Runner
|
||||||
|
implementieren muss, damit alle Commands vollständig unterstützt werden.
|
||||||
|
|
||||||
|
## Modulform (Pflicht)
|
||||||
|
|
||||||
|
- Der Runner ist ein Lua-Modul, das eine Table mit Funktionen zurückgibt.
|
||||||
|
- Beispiel:
|
||||||
|
- `local runner = {}`
|
||||||
|
- `return runner`
|
||||||
|
- Der Modulpfad in `runner_modules` muss exakt zum Dateipfad unter `lua/` passen.
|
||||||
|
- Beispiel: `lua/test-samurai-go-runner/init.lua` -> `require("test-samurai-go-runner")`.
|
||||||
|
- `lua/init.lua` wäre `require("init")` und ist kollisionsanfällig; vermeiden.
|
||||||
|
|
||||||
|
## Pflichtfunktionen (volle Command- und Keymap-Unterstützung)
|
||||||
|
|
||||||
|
- `is_test_file(bufnr) -> boolean`
|
||||||
|
- Wird für die Runner-Auswahl genutzt.
|
||||||
|
- `find_nearest(bufnr, row, col) -> spec|nil, err?`
|
||||||
|
- Für `TSamNearest`.
|
||||||
|
- `spec.file` muss gesetzt sein.
|
||||||
|
- Bei Fehler/kein Treffer: `nil, "reason"` zurückgeben.
|
||||||
|
- `build_command(spec) -> command_spec`
|
||||||
|
- Für `TSamNearest`.
|
||||||
|
- `build_file_command(bufnr) -> command_spec`
|
||||||
|
- Für `TSamFile`.
|
||||||
|
- `build_all_command(bufnr) -> command_spec`
|
||||||
|
- Für `TSamAll`.
|
||||||
|
- `build_failed_command(last_command, failures, scope_kind) -> command_spec`
|
||||||
|
- Für `TSamFailedOnly`.
|
||||||
|
- `parse_results(output) -> results`
|
||||||
|
- Fallback-Parser für vollständigen Output.
|
||||||
|
- `output_parser() -> { on_line, on_complete }`
|
||||||
|
- Muss Streaming unterstützen (Listing wird live befüllt).
|
||||||
|
- `parse_test_output(output) -> table`
|
||||||
|
- Detail-Ausgabe pro Test (Detail-Float und `<cr>` im Listing).
|
||||||
|
- `collect_failed_locations(failures, command, scope_kind) -> items`
|
||||||
|
- Quickfix-Unterstützung (Keymap `<leader>qn`).
|
||||||
|
|
||||||
|
## Output-Parsing (Listing + Summary)
|
||||||
|
|
||||||
|
Beide Varianten müssen vorhanden sein:
|
||||||
|
|
||||||
|
- `parse_results(output) -> results`
|
||||||
|
- `results` muss enthalten:
|
||||||
|
- `passes` (Array von Namen)
|
||||||
|
- `failures` (Array von Namen)
|
||||||
|
- `skips` (Array von Namen)
|
||||||
|
- Optional:
|
||||||
|
- `display = { passes = {}, failures = {}, skips = {} }`
|
||||||
|
- `failures_all` (für Streaming-Parser, um alle bisherigen Failures zu liefern)
|
||||||
|
- Wenn `display` fehlt, werden `passes/failures/skips` direkt im Listing angezeigt.
|
||||||
|
- oder `output_parser() -> { on_line, on_complete }`
|
||||||
|
- `on_line(line, state)` kann `results` liefern (siehe oben).
|
||||||
|
- `on_complete(output, state)` kann `results` liefern (siehe oben).
|
||||||
|
- `on_line` muss Ergebnisse liefern, damit das Listing-Float immer gestreamt wird.
|
||||||
|
|
||||||
|
Hinweis: `parse_results` darf intern `output_parser().on_complete` nutzen, aber beide Funktionen müssen existieren.
|
||||||
|
|
||||||
|
## Listing-Gruppierung fuer Parent/Subtests
|
||||||
|
|
||||||
|
- Wenn Testnamen das Format `Parent/Subtest` oder verschachtelt `Parent/Sub/Subtest` haben
|
||||||
|
und der Parent ebenfalls in den Ergebnissen vorhanden ist, gruppiert das Listing:
|
||||||
|
- Parent kommt vor seinen direkten Kindern.
|
||||||
|
- Mehrstufige Subtests werden hierarchisch gruppiert (Parent -> Kind -> Enkel).
|
||||||
|
- Die Reihenfolge der Kinder folgt der Eingangsreihenfolge des Runners.
|
||||||
|
- Ohne Parent-Eintrag bleibt die normale Reihenfolge erhalten.
|
||||||
|
|
||||||
|
## Detail-Output (TSamShowOutput / <cr> im Listing)
|
||||||
|
|
||||||
|
- `parse_test_output(output) -> table`
|
||||||
|
- Rückgabeform:
|
||||||
|
- `{ [test_name] = { "line1", "line2", ... } }`
|
||||||
|
- `test_name` muss mit `results.*` korrespondieren (gleiches Namensschema).
|
||||||
|
|
||||||
|
## Quickfix-Unterstützung (Failures)
|
||||||
|
|
||||||
|
- `collect_failed_locations(failures, command, scope_kind) -> items`
|
||||||
|
- `items`: Array von Quickfix-Items
|
||||||
|
- `{ filename = "...", lnum = <number>, col = <number>, text = "..." }`
|
||||||
|
|
||||||
|
## Erwartete Datenformen
|
||||||
|
|
||||||
|
- `command_spec`:
|
||||||
|
- `{ cmd = { "binary", "arg1", ... }, cwd = "..." }`
|
||||||
|
- `cmd` darf nicht leer sein.
|
||||||
|
- `cwd` ist optional; wenn nicht gesetzt, nutzt der Core das aktuelle CWD.
|
||||||
|
- `spec` (von `find_nearest`):
|
||||||
|
- Muss mindestens `file` enthalten, z. B.:
|
||||||
|
- `{ file = "...", cwd = "...", test_name = "...", full_name = "...", kind = "..." }`
|
||||||
|
- `results` (für Listing-Float + Keymaps):
|
||||||
|
- `{ passes = { "Name1", ... }, failures = { "Name2", ... }, skips = { "Name3", ... } }`
|
||||||
|
- Optional: `display = { passes = { "DisplayName1", ... }, failures = { "DisplayName2", ... }, skips = { "DisplayName3", ... } }`
|
||||||
|
- `failures` steuert `[ FAIL ]`-Zeilen im Listing und wird von `<leader>nf`/`<leader>pf` genutzt.
|
||||||
|
- `items` (für Quickfix):
|
||||||
|
- `{ { filename = "...", lnum = 1, col = 1, text = "..." }, ... }`
|
||||||
|
- Wird von `<leader>qn` verwendet.
|
||||||
|
|
||||||
|
## Keymaps (Datenlieferung)
|
||||||
|
|
||||||
|
- `<leader>nf` / `<leader>pf`
|
||||||
|
- benötigt `[ FAIL ]`-Einträge im Listing.
|
||||||
|
- Runner muss `results.failures` (und optional `display.failures`) liefern.
|
||||||
|
- `<leader>qn`
|
||||||
|
- springt in die Quickfix-Liste.
|
||||||
|
- Runner muss `collect_failed_locations` implementieren und gültige `items` liefern.
|
||||||
|
- `<cr>` im Listing
|
||||||
|
- öffnet Detail-Float.
|
||||||
|
- Runner muss `parse_test_output` liefern und Testnamen konsistent zu `results.*` halten.
|
||||||
|
|
||||||
|
## Optional empfohlene Metadaten
|
||||||
|
|
||||||
|
- `name` (String)
|
||||||
|
- Wird in Fehlermeldungen und Logs angezeigt.
|
||||||
|
- `framework` (String)
|
||||||
|
- Wird zur Framework-Auswahl (z. B. JS) genutzt.
|
||||||
|
|
||||||
|
## Prompt-Beispiel
|
||||||
|
|
||||||
|
"Erstelle mir anhand der `runner-agents.md` einen neuen Runner für Rust."
|
||||||
|
|
||||||
|
## Minimaler Runner-Skeleton (Template)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local runner = {
|
||||||
|
name = "my-runner",
|
||||||
|
framework = "my-framework",
|
||||||
|
}
|
||||||
|
|
||||||
|
function runner.is_test_file(bufnr)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.find_nearest(bufnr, row, col)
|
||||||
|
return nil, "no test call found"
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_command(spec)
|
||||||
|
return { cmd = { "echo", "not-implemented" }, cwd = spec.cwd }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_file_command(bufnr)
|
||||||
|
return { cmd = { "echo", "not-implemented" } }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_all_command(bufnr)
|
||||||
|
return { cmd = { "echo", "not-implemented" } }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_failed_command(last_command, failures, scope_kind)
|
||||||
|
return { cmd = { "echo", "not-implemented" }, cwd = last_command and last_command.cwd or nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.parse_results(output)
|
||||||
|
return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } }
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.output_parser()
|
||||||
|
return {
|
||||||
|
on_line = function(_line, _state)
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
|
on_complete = function(_output, _state)
|
||||||
|
return runner.parse_results(_output)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.parse_test_output(output)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.collect_failed_locations(failures, command, scope_kind)
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
return runner
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checkliste für neue Runner
|
||||||
|
|
||||||
|
- is_test_file implementiert
|
||||||
|
- find_nearest implementiert (setzt `spec.file`)
|
||||||
|
- build_command implementiert
|
||||||
|
- build_file_command implementiert
|
||||||
|
- build_all_command implementiert
|
||||||
|
- build_failed_command implementiert
|
||||||
|
- parse_results implementiert
|
||||||
|
- output_parser implementiert (Streaming)
|
||||||
|
- parse_test_output implementiert
|
||||||
|
- collect_failed_locations implementiert
|
||||||
|
- command_spec `{ cmd, cwd }` korrekt zurückgegeben
|
||||||
|
|
||||||
|
## Projekt- und Prozessanforderungen
|
||||||
|
|
||||||
|
- Rolle: **TDD-first Entwickler**.
|
||||||
|
- Jede neue Funktion, jedes neue Kommando und jede Verhaltensänderung muss durch Tests abgesichert sein.
|
||||||
|
- Nach jeder Code-Änderung Tests via `bash run_test.sh` ausführen und bei Fehlern korrigieren, bis alles grün ist.
|
||||||
|
- **Nicht raten**:
|
||||||
|
- Bei unklaren oder mehrdeutigen Anforderungen Arbeit stoppen und Klarstellung verlangen.
|
||||||
|
- TODO/NOTE im Code ist zulässig, stilles Raten nicht.
|
||||||
|
- **Keine stillen Änderungen**:
|
||||||
|
- Bestehende Features dürfen nicht unbemerkt geändert oder ersetzt werden.
|
||||||
|
- Notwendige Anpassungen zur Koexistenz mehrerer Features müssen klar erkennbar sein.
|
||||||
|
- Antworten immer auf Deutsch.
|
||||||
|
- Eine englischsprachige `README.md` ist zu erstellen und wird bei Änderungen automatisch aktualisiert.
|
||||||
|
- TDD-Vorgaben (aus `AGENTS.md`) uebernehmen:
|
||||||
|
- Neue Funktionen/Commands/Verhaltensaenderungen muessen getestet werden.
|
||||||
|
- Tests nach jeder Code-Aenderung ausfuehren.
|
||||||
|
- Im Runner erstellter Quellcode ist ebenfalls zu testen.
|
||||||
|
- Eine eigene `run_test.sh` wird im Runner-Repo angelegt.
|
||||||
|
- Eine Gitea-Action ist zu erstellen, die bei jedem Push die Tests ausfuehrt.
|
||||||
|
- Neovim wird per AppImage installiert (kein `apt`).
|
||||||
|
- Runner laeuft auf `gitea-act-runner` mit Raspberry Pi 5 (ARM).
|
||||||
|
- Beispiel (anpassbarer Workflow):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Neovim (AppImage)
|
||||||
|
run: |
|
||||||
|
curl -L -o nvim.appimage https://github.com/neovim/neovim/releases/download/v0.11.4/nvim.appimage
|
||||||
|
chmod +x nvim.appimage
|
||||||
|
sudo mv nvim.appimage /usr/local/bin/nvim
|
||||||
|
- name: Run tests
|
||||||
|
run: bash run_test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zusaetzliche Runner-Guidelines (framework-agnostisch)
|
||||||
|
|
||||||
|
- **Testnamen-Konvention:** Runner sollen eine konsistente, dokumentierte Full-Name-Bildung verwenden (z. B. `Parent/Subtest`), inklusive Mehrfach-Nesting. Diese Konvention muss in `results.*`, `parse_test_output` und `collect_failed_locations` uebereinstimmen.
|
||||||
|
- **TSamNearest-Prioritaet:** Falls moeglich, gelten folgende Regeln: Test-Block > Describe/Context-Block > File-Command. Das Verhalten muss getestet werden (Cursor im Test, zwischen Tests, ausserhalb von Describe/Context).
|
||||||
|
- **Reporter-Payload-Schema:** Wenn ein Custom-Reporter verwendet wird, soll dessen JSON-Payload dokumentiert und stabil sein (z. B. `{ name, status, file, location, output }`), damit Parser/Quickfix/Detail-Output konsistent bleiben.
|
||||||
|
- **Failed-Only-Logik:** Failed-Only muss auf den letzten Fehlermeldungen basieren und nur die fehlerhaften Tests erneut ausfuehren. Die Pattern-Strategie (z. B. Titel-only vs. Full-Name) muss getestet werden.
|
||||||
|
- **CI-Installations-Snippet:** Die Neovim-Installation in CI soll als „authoritative snippet“ behandelt werden und in Runner-Repos 1:1 uebernommen werden.
|
||||||
@@ -1,2 +1,8 @@
|
|||||||
vim.opt.runtimepath:append(vim.loop.cwd())
|
local cwd = vim.loop.cwd()
|
||||||
|
vim.opt.runtimepath:append(cwd)
|
||||||
|
package.path = table.concat({
|
||||||
|
cwd .. "/lua/?.lua",
|
||||||
|
cwd .. "/lua/?/init.lua",
|
||||||
|
package.path,
|
||||||
|
}, ";")
|
||||||
require("plenary.busted")
|
require("plenary.busted")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,339 +0,0 @@
|
|||||||
local test_samurai = require("test-samurai")
|
|
||||||
local core = require("test-samurai.core")
|
|
||||||
|
|
||||||
local function mkbuf(path, ft, lines)
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = ft
|
|
||||||
if lines then
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
||||||
end
|
|
||||||
return bufnr
|
|
||||||
end
|
|
||||||
|
|
||||||
local function stub_jobstart(opts_config)
|
|
||||||
local calls = {}
|
|
||||||
local orig = vim.fn.jobstart
|
|
||||||
local idx = 0
|
|
||||||
local config = opts_config or {}
|
|
||||||
vim.fn.jobstart = function(cmd, opts)
|
|
||||||
idx = idx + 1
|
|
||||||
table.insert(calls, { cmd = cmd, opts = opts })
|
|
||||||
local code = 0
|
|
||||||
if type(config.exit_codes) == "table" then
|
|
||||||
code = config.exit_codes[idx] or 0
|
|
||||||
elseif type(config.exit_codes) == "number" then
|
|
||||||
code = config.exit_codes
|
|
||||||
end
|
|
||||||
local out = config.stdout and config.stdout[idx] or nil
|
|
||||||
if out and opts and opts.on_stdout then
|
|
||||||
if type(out) == "string" then
|
|
||||||
out = { out }
|
|
||||||
end
|
|
||||||
opts.on_stdout(1, out, nil)
|
|
||||||
end
|
|
||||||
local err = config.stderr and config.stderr[idx] or nil
|
|
||||||
if err and opts and opts.on_stderr then
|
|
||||||
if type(err) == "string" then
|
|
||||||
err = { err }
|
|
||||||
end
|
|
||||||
opts.on_stderr(1, err, nil)
|
|
||||||
end
|
|
||||||
if opts and opts.on_exit then
|
|
||||||
opts.on_exit(1, code, nil)
|
|
||||||
end
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
return calls, orig
|
|
||||||
end
|
|
||||||
|
|
||||||
describe("TSamFailedOnly", function()
|
|
||||||
before_each(function()
|
|
||||||
test_samurai.setup()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("reruns failed jest tests with --onlyFailures", function()
|
|
||||||
local json = vim.json.encode({
|
|
||||||
testResults = {
|
|
||||||
{
|
|
||||||
assertionResults = {
|
|
||||||
{ status = "passed", title = "inner 1", fullName = "outer inner 1" },
|
|
||||||
{ status = "failed", title = "inner 2", fullName = "outer inner 2" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
local calls, orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1, 0 },
|
|
||||||
stdout = { { json } },
|
|
||||||
})
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/foo_failed_only.test.ts", "typescript", {
|
|
||||||
'describe("outer", function() {',
|
|
||||||
' it("inner 1", function() {',
|
|
||||||
" -- inside 1",
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' it("inner 2", function() {',
|
|
||||||
" -- inside 2",
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
core.run_failed_only()
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
|
||||||
assert.are.same(
|
|
||||||
{ "npx", "jest", "--json", "--verbose", "/tmp/project/foo_failed_only.test.ts", "-t", "inner 2" },
|
|
||||||
calls[1].cmd
|
|
||||||
)
|
|
||||||
assert.are.same(
|
|
||||||
{ "npx", "jest", "--json", "--verbose", "-t", "outer inner 2", "/tmp/project/foo_failed_only.test.ts" },
|
|
||||||
calls[2].cmd
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("falls back to TSamLast when last run had no failures", function()
|
|
||||||
local json = vim.json.encode({
|
|
||||||
testResults = {
|
|
||||||
{
|
|
||||||
assertionResults = {
|
|
||||||
{ status = "passed", title = "inner 1", fullName = "outer inner 1" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
local calls, orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 0, 0 },
|
|
||||||
stdout = { { json } },
|
|
||||||
})
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/foo_failed_only_pass.test.ts", "typescript", {
|
|
||||||
'describe("outer", function() {',
|
|
||||||
' it("inner 1", function() {',
|
|
||||||
" -- inside 1",
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' it("inner 2", function() {',
|
|
||||||
" -- inside 2",
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
core.run_failed_only()
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
|
||||||
assert.are.same(calls[1].cmd, calls[2].cmd)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("reruns failed go tests with -run regex", function()
|
|
||||||
local json_lines = {
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestFoo/first" }),
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestBar" }),
|
|
||||||
}
|
|
||||||
|
|
||||||
local calls, orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1, 0 },
|
|
||||||
stdout = { json_lines },
|
|
||||||
})
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/foo_failed_only_test.go", "go", {
|
|
||||||
"package main",
|
|
||||||
"import \"testing\"",
|
|
||||||
"",
|
|
||||||
"func TestFoo(t *testing.T) {",
|
|
||||||
" t.Run(\"first\", func(t *testing.T) {",
|
|
||||||
" -- inside first",
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
"",
|
|
||||||
"func TestBar(t *testing.T) {",
|
|
||||||
" -- inside bar",
|
|
||||||
"}",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
|
|
||||||
core.run_all()
|
|
||||||
core.run_failed_only()
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
|
||||||
assert.are.same({ "go", "test", "-json", "./..." }, calls[1].cmd)
|
|
||||||
assert.are.same({ "go", "test", "-json", "./...", "-run", "^(TestFoo/first|TestBar)$" }, calls[2].cmd)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("uses go parser for failed-only output (no raw JSON)", function()
|
|
||||||
local json_line = vim.json.encode({
|
|
||||||
Action = "fail",
|
|
||||||
Test = "TestHandleGet/returns_200",
|
|
||||||
})
|
|
||||||
|
|
||||||
local calls, orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1, 1 },
|
|
||||||
stdout = { { json_line }, { json_line } },
|
|
||||||
})
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/foo_failed_only_output_test.go", "go", {
|
|
||||||
"package main",
|
|
||||||
"import \"testing\"",
|
|
||||||
"",
|
|
||||||
"func TestHandleGet(t *testing.T) {",
|
|
||||||
" t.Run(\"returns_200\", func(t *testing.T) {",
|
|
||||||
" -- inside test",
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
|
|
||||||
core.run_all()
|
|
||||||
core.run_failed_only()
|
|
||||||
|
|
||||||
local out_buf = vim.api.nvim_get_current_buf()
|
|
||||||
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
|
||||||
local has_raw = false
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
if line == json_line then
|
|
||||||
has_raw = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
assert.is_false(has_raw)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("reruns failed mocha tests from json-stream array output without raw JSON", function()
|
|
||||||
test_samurai.setup({
|
|
||||||
runner_modules = {
|
|
||||||
"test-samurai.runners.js-mocha",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
local fail_line = vim.json.encode({
|
|
||||||
event = "fail",
|
|
||||||
fullTitle = "API :: /brands... GET: /",
|
|
||||||
})
|
|
||||||
local start_line = vim.json.encode({ "start", { total = 1 } })
|
|
||||||
local end_line = vim.json.encode({ "end", { tests = 0 } })
|
|
||||||
|
|
||||||
local calls, orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1, 1 },
|
|
||||||
stdout = { { fail_line }, { start_line, end_line } },
|
|
||||||
})
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/brands.test.js", "javascript", {
|
|
||||||
'describe("API :: /brands...", function() {',
|
|
||||||
' it("GET: /", function() {',
|
|
||||||
" -- inside test",
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 3, 0 })
|
|
||||||
|
|
||||||
core.run_file()
|
|
||||||
core.run_failed_only()
|
|
||||||
|
|
||||||
local out_buf = vim.api.nvim_get_current_buf()
|
|
||||||
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
|
||||||
assert.are.same(
|
|
||||||
{ "npx", "mocha", "--reporter", "json-stream", "/tmp/project/brands.test.js" },
|
|
||||||
calls[1].cmd
|
|
||||||
)
|
|
||||||
local failed_cmd = calls[2].cmd or {}
|
|
||||||
local saw_grep = false
|
|
||||||
local saw_fgrep = false
|
|
||||||
local saw_title = false
|
|
||||||
local plain_title = "API :: /brands... GET: /"
|
|
||||||
for _, arg in ipairs(failed_cmd) do
|
|
||||||
if arg == "--grep" then
|
|
||||||
saw_grep = true
|
|
||||||
elseif arg == "--fgrep" then
|
|
||||||
saw_fgrep = true
|
|
||||||
elseif arg == plain_title then
|
|
||||||
saw_title = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
assert.is_false(saw_grep)
|
|
||||||
assert.is_true(saw_fgrep)
|
|
||||||
assert.is_true(saw_title)
|
|
||||||
|
|
||||||
local has_raw = false
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
if line == start_line or line == end_line then
|
|
||||||
has_raw = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
assert.is_false(has_raw)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("does not affect TSamLast history", function()
|
|
||||||
local json = vim.json.encode({
|
|
||||||
testResults = {
|
|
||||||
{
|
|
||||||
assertionResults = {
|
|
||||||
{ status = "failed", title = "inner 2", fullName = "outer inner 2" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
local calls, orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1, 1, 1 },
|
|
||||||
stdout = { { json } },
|
|
||||||
})
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/foo_failed_only_last.test.ts", "typescript", {
|
|
||||||
'describe("outer", function() {',
|
|
||||||
' it("inner 1", function() {',
|
|
||||||
" -- inside 1",
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' it("inner 2", function() {',
|
|
||||||
" -- inside 2",
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
core.run_failed_only()
|
|
||||||
core.run_last()
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(3, #calls)
|
|
||||||
assert.are.same(calls[1].cmd, calls[3].cmd)
|
|
||||||
assert.are.same(
|
|
||||||
{ "npx", "jest", "--json", "--verbose", "-t", "outer inner 2", "/tmp/project/foo_failed_only_last.test.ts" },
|
|
||||||
calls[2].cmd
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
local go_runner = require("test-samurai.runners.go")
|
|
||||||
local util = require("test-samurai.util")
|
|
||||||
|
|
||||||
describe("test-samurai go runner", function()
|
|
||||||
it("detects Go test files by suffix", function()
|
|
||||||
local bufnr1 = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr1, "/tmp/go_suffix_test.go")
|
|
||||||
assert.is_true(go_runner.is_test_file(bufnr1))
|
|
||||||
|
|
||||||
local bufnr2 = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr2, "/tmp/go_main.go")
|
|
||||||
assert.is_false(go_runner.is_test_file(bufnr2))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("finds subtest when cursor is inside t.Run block", function()
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/go_subtest_test.go")
|
|
||||||
local lines = {
|
|
||||||
"package main",
|
|
||||||
"import \"testing\"",
|
|
||||||
"",
|
|
||||||
"func TestFoo(t *testing.T) {",
|
|
||||||
" t.Run(\"first\", func(t *testing.T) {",
|
|
||||||
" -- inside first",
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
" t.Run(\"second\", func(t *testing.T) {",
|
|
||||||
" -- inside second",
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
}
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
||||||
|
|
||||||
local orig_fs_find = vim.fs.find
|
|
||||||
vim.fs.find = function(markers, opts)
|
|
||||||
return { "/tmp/go.mod" }
|
|
||||||
end
|
|
||||||
|
|
||||||
local row_inside_first = 5
|
|
||||||
local spec, err = go_runner.find_nearest(bufnr, row_inside_first, 0)
|
|
||||||
|
|
||||||
vim.fs.find = orig_fs_find
|
|
||||||
|
|
||||||
assert.is_nil(err)
|
|
||||||
assert.is_not_nil(spec)
|
|
||||||
assert.equals("TestFoo/first", spec.test_path)
|
|
||||||
assert.equals("subtest", spec.scope)
|
|
||||||
assert.is_true(spec.file:match("go_subtest_test%.go$") ~= nil)
|
|
||||||
assert.is_true(spec.cwd:match("tmp$") ~= nil)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("falls back to whole test function when between subtests", function()
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/go_between_test.go")
|
|
||||||
local lines = {
|
|
||||||
"package main",
|
|
||||||
"import \"testing\"",
|
|
||||||
"",
|
|
||||||
"func TestFoo(t *testing.T) {",
|
|
||||||
" t.Run(\"first\", func(t *testing.T) {",
|
|
||||||
" -- inside first",
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
" t.Run(\"second\", func(t *testing.T) {",
|
|
||||||
" -- inside second",
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
}
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
||||||
|
|
||||||
local orig_fs_find = vim.fs.find
|
|
||||||
vim.fs.find = function(markers, opts)
|
|
||||||
return { "/tmp/go.mod" }
|
|
||||||
end
|
|
||||||
|
|
||||||
local row_between = 7
|
|
||||||
local spec, err = go_runner.find_nearest(bufnr, row_between, 0)
|
|
||||||
|
|
||||||
vim.fs.find = orig_fs_find
|
|
||||||
|
|
||||||
assert.is_nil(err)
|
|
||||||
assert.is_not_nil(spec)
|
|
||||||
assert.equals("TestFoo", spec.test_path)
|
|
||||||
assert.equals("function", spec.scope)
|
|
||||||
assert.is_true(spec.file:match("go_between_test%.go$") ~= nil)
|
|
||||||
assert.is_true(spec.cwd:match("tmp$") ~= nil)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("build_command uses current package and correct run pattern", function()
|
|
||||||
local spec_sub = {
|
|
||||||
file = "/tmp/project/pkg/foo_test.go",
|
|
||||||
cwd = "/tmp/project",
|
|
||||||
test_path = "TestFoo/first",
|
|
||||||
scope = "subtest",
|
|
||||||
}
|
|
||||||
|
|
||||||
local cmd_spec_sub = go_runner.build_command(spec_sub)
|
|
||||||
assert.are.same(
|
|
||||||
{ "go", "test", "-json", "./pkg", "-run", "^TestFoo/first$" },
|
|
||||||
cmd_spec_sub.cmd
|
|
||||||
)
|
|
||||||
|
|
||||||
local spec_func = {
|
|
||||||
file = "/tmp/project/foo_test.go",
|
|
||||||
cwd = "/tmp/project",
|
|
||||||
test_path = "TestFoo",
|
|
||||||
scope = "function",
|
|
||||||
}
|
|
||||||
|
|
||||||
local cmd_spec_func = go_runner.build_command(spec_func)
|
|
||||||
assert.are.same(
|
|
||||||
{ "go", "test", "-json", "./", "-run", "^TestFoo($|/)" },
|
|
||||||
cmd_spec_func.cmd
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("build_file_command uses exact test names from current file", function()
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/project/get_test.go")
|
|
||||||
local lines = {
|
|
||||||
"package main",
|
|
||||||
"import \"testing\"",
|
|
||||||
"",
|
|
||||||
"func TestHandleGet(t *testing.T) {",
|
|
||||||
" t.Run(\"returns_200\", func(t *testing.T) {",
|
|
||||||
" -- inside test",
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
}
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
||||||
|
|
||||||
local orig_find_root = util.find_root
|
|
||||||
util.find_root = function(path, markers)
|
|
||||||
return "/tmp/project"
|
|
||||||
end
|
|
||||||
|
|
||||||
local cmd_spec = go_runner.build_file_command(bufnr)
|
|
||||||
|
|
||||||
util.find_root = orig_find_root
|
|
||||||
|
|
||||||
assert.are.same(
|
|
||||||
{ "go", "test", "-json", "./", "-run", "^(TestHandleGet)$" },
|
|
||||||
cmd_spec.cmd
|
|
||||||
)
|
|
||||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
local jest = require("test-samurai.runners.js-jest")
|
|
||||||
local mocha = require("test-samurai.runners.js-mocha")
|
|
||||||
local vitest = require("test-samurai.runners.js-vitest")
|
|
||||||
local util = require("test-samurai.util")
|
|
||||||
|
|
||||||
describe("test-samurai js runner (jest)", function()
|
|
||||||
it("detects JS/TS test files by name and filetype", function()
|
|
||||||
local bufnr1 = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr1, "/tmp/foo_detect.test.ts")
|
|
||||||
vim.bo[bufnr1].filetype = "typescript"
|
|
||||||
assert.is_true(jest.is_test_file(bufnr1))
|
|
||||||
|
|
||||||
local bufnr2 = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr2, "/tmp/foo_detect.ts")
|
|
||||||
vim.bo[bufnr2].filetype = "typescript"
|
|
||||||
assert.is_false(jest.is_test_file(bufnr2))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("finds nearest it() call as test name and builds full_name when cursor is inside the test", function()
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/foo_nearest.test.ts")
|
|
||||||
vim.bo[bufnr].filetype = "typescript"
|
|
||||||
local lines = {
|
|
||||||
'describe("outer", function() {',
|
|
||||||
' it("inner 1", function() {',
|
|
||||||
" -- inside 1",
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' it("inner 2", function() {',
|
|
||||||
" -- inside 2",
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
}
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
||||||
|
|
||||||
local orig_fs_find = vim.fs.find
|
|
||||||
vim.fs.find = function(names, opts)
|
|
||||||
return { "/tmp/package.json" }
|
|
||||||
end
|
|
||||||
|
|
||||||
local row_inside_second = 6
|
|
||||||
local spec, err = jest.find_nearest(bufnr, row_inside_second, 0)
|
|
||||||
|
|
||||||
vim.fs.find = orig_fs_find
|
|
||||||
|
|
||||||
assert.is_nil(err)
|
|
||||||
assert.is_not_nil(spec)
|
|
||||||
assert.equals("inner 2", spec.test_name)
|
|
||||||
assert.equals("outer inner 2", spec.full_name)
|
|
||||||
assert.equals("jest", spec.framework)
|
|
||||||
assert.is_true(spec.file:match("foo_nearest%.test%.ts$") ~= nil)
|
|
||||||
assert.is_true(spec.cwd:match("tmp$") ~= nil)
|
|
||||||
|
|
||||||
local cmd_spec = jest.build_command(spec)
|
|
||||||
assert.are.same({ "npx", "jest", "--json", "--verbose", spec.file, "-t", "inner 2" }, cmd_spec.cmd)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("returns describe block when cursor is between it() calls", function()
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/foo_between.test.ts")
|
|
||||||
vim.bo[bufnr].filetype = "typescript"
|
|
||||||
local lines = {
|
|
||||||
'describe("outer", function() {',
|
|
||||||
' it("inner 1", function() {',
|
|
||||||
" -- inside 1",
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' it("inner 2", function() {',
|
|
||||||
" -- inside 2",
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
}
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
||||||
|
|
||||||
local orig_fs_find = vim.fs.find
|
|
||||||
vim.fs.find = function(names, opts)
|
|
||||||
return { "/tmp/package.json" }
|
|
||||||
end
|
|
||||||
|
|
||||||
local row_between = 4
|
|
||||||
local spec, err = jest.find_nearest(bufnr, row_between, 0)
|
|
||||||
|
|
||||||
vim.fs.find = orig_fs_find
|
|
||||||
|
|
||||||
assert.is_nil(err)
|
|
||||||
assert.is_not_nil(spec)
|
|
||||||
assert.equals("outer", spec.test_name)
|
|
||||||
assert.equals("outer", spec.full_name)
|
|
||||||
assert.equals("jest", spec.framework)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("treats jest.config in test/.bin as project root parent", function()
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/foo_binroot.test.ts")
|
|
||||||
vim.bo[bufnr].filetype = "typescript"
|
|
||||||
local lines = {
|
|
||||||
'describe("outer", function() {',
|
|
||||||
' it("inner 1", function() {',
|
|
||||||
" -- inside 1",
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' it("inner 2", function() {',
|
|
||||||
" -- inside 2",
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
}
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
||||||
|
|
||||||
local orig_fs_find = vim.fs.find
|
|
||||||
vim.fs.find = function(names, opts)
|
|
||||||
return { "/tmp/test/.bin/jest.config.js" }
|
|
||||||
end
|
|
||||||
|
|
||||||
local row_inside_second = 6
|
|
||||||
local spec, err = jest.find_nearest(bufnr, row_inside_second, 0)
|
|
||||||
|
|
||||||
vim.fs.find = orig_fs_find
|
|
||||||
|
|
||||||
assert.is_nil(err)
|
|
||||||
assert.is_not_nil(spec)
|
|
||||||
assert.equals("/tmp", spec.cwd)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe("test-samurai js runner (mocha)", function()
|
|
||||||
it("builds mocha command with fgrep and full test title", function()
|
|
||||||
local spec = {
|
|
||||||
file = "/tmp/project/test/foo_nearest.test.ts",
|
|
||||||
cwd = "/tmp/project",
|
|
||||||
test_name = "inner 2",
|
|
||||||
full_name = "outer inner 2",
|
|
||||||
}
|
|
||||||
|
|
||||||
local cmd_spec = mocha.build_command(spec)
|
|
||||||
|
|
||||||
assert.are.same(
|
|
||||||
{ "npx", "mocha", "--reporter", "json-stream", "--fgrep", "outer inner 2", spec.file },
|
|
||||||
cmd_spec.cmd
|
|
||||||
)
|
|
||||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("builds mocha all command with default glob", function()
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/project/test/foo_all.test.js")
|
|
||||||
vim.bo[bufnr].filetype = "javascript"
|
|
||||||
|
|
||||||
local orig_find_root = util.find_root
|
|
||||||
util.find_root = function(path, markers)
|
|
||||||
return "/tmp/project"
|
|
||||||
end
|
|
||||||
|
|
||||||
local cmd_spec = mocha.build_all_command(bufnr)
|
|
||||||
|
|
||||||
util.find_root = orig_find_root
|
|
||||||
|
|
||||||
assert.are.same(
|
|
||||||
{ "npx", "mocha", "--reporter", "json-stream", "test/**/*.test.js" },
|
|
||||||
cmd_spec.cmd
|
|
||||||
)
|
|
||||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe("test-samurai js runner (vitest)", function()
|
|
||||||
it("builds vitest command with tap-flat reporter", function()
|
|
||||||
local spec = {
|
|
||||||
file = "/tmp/project/test/foo_nearest.test.ts",
|
|
||||||
cwd = "/tmp/project",
|
|
||||||
test_name = "inner 2",
|
|
||||||
full_name = "outer inner 2",
|
|
||||||
}
|
|
||||||
|
|
||||||
local cmd_spec = vitest.build_command(spec)
|
|
||||||
|
|
||||||
assert.are.same(
|
|
||||||
{ "npx", "vitest", "--reporter", "tap-flat", spec.file, "-t", "inner 2" },
|
|
||||||
cmd_spec.cmd
|
|
||||||
)
|
|
||||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("builds vitest all command with tap-flat reporter", function()
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/project/test/foo_all.test.ts")
|
|
||||||
vim.bo[bufnr].filetype = "typescript"
|
|
||||||
|
|
||||||
local orig_find_root = util.find_root
|
|
||||||
util.find_root = function(path, markers)
|
|
||||||
return "/tmp/project"
|
|
||||||
end
|
|
||||||
|
|
||||||
local cmd_spec = vitest.build_all_command(bufnr)
|
|
||||||
|
|
||||||
util.find_root = orig_find_root
|
|
||||||
|
|
||||||
assert.are.same(
|
|
||||||
{ "npx", "vitest", "--reporter", "tap-flat" },
|
|
||||||
cmd_spec.cmd
|
|
||||||
)
|
|
||||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
local test_samurai = require("test-samurai")
|
|
||||||
local core = require("test-samurai.core")
|
|
||||||
|
|
||||||
local function mkbuf(path, ft, lines)
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = ft
|
|
||||||
if lines then
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
||||||
end
|
|
||||||
return bufnr
|
|
||||||
end
|
|
||||||
|
|
||||||
local function capture_jobstart()
|
|
||||||
local calls = {}
|
|
||||||
local orig = vim.fn.jobstart
|
|
||||||
vim.fn.jobstart = function(cmd, opts)
|
|
||||||
table.insert(calls, { cmd = cmd, opts = opts })
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
return calls, orig
|
|
||||||
end
|
|
||||||
|
|
||||||
describe("TSamLast", function()
|
|
||||||
before_each(function()
|
|
||||||
test_samurai.setup()
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("reruns last Go command", function()
|
|
||||||
local calls, orig_jobstart = capture_jobstart()
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/foo_test.go", "go", {
|
|
||||||
"package main",
|
|
||||||
"import \"testing\"",
|
|
||||||
"",
|
|
||||||
"func TestFoo(t *testing.T) {",
|
|
||||||
" t.Run(\"first\", func(t *testing.T) {",
|
|
||||||
" -- inside first",
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 6, 0 })
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
core.run_last()
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
|
||||||
assert.are.same({ "go", "test", "-json", "./", "-run", "^TestFoo/first$" }, calls[1].cmd)
|
|
||||||
assert.are.same(calls[1].cmd, calls[2].cmd)
|
|
||||||
assert.equals(calls[1].opts.cwd, calls[2].opts.cwd)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("uses go parser for TSamLast output (no raw JSON)", function()
|
|
||||||
local json_line = vim.json.encode({
|
|
||||||
Action = "fail",
|
|
||||||
Test = "TestHandleGet/returns_200",
|
|
||||||
})
|
|
||||||
|
|
||||||
local calls = {}
|
|
||||||
local orig_jobstart = vim.fn.jobstart
|
|
||||||
vim.fn.jobstart = function(cmd, opts)
|
|
||||||
table.insert(calls, { cmd = cmd, opts = opts })
|
|
||||||
if opts and opts.on_stdout then
|
|
||||||
opts.on_stdout(1, { json_line }, nil)
|
|
||||||
end
|
|
||||||
if opts and opts.on_exit then
|
|
||||||
opts.on_exit(1, 1, nil)
|
|
||||||
end
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/foo_last_output_test.go", "go", {
|
|
||||||
"package main",
|
|
||||||
"import \"testing\"",
|
|
||||||
"",
|
|
||||||
"func TestHandleGet(t *testing.T) {",
|
|
||||||
" t.Run(\"returns_200\", func(t *testing.T) {",
|
|
||||||
" -- inside test",
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
|
|
||||||
core.run_all()
|
|
||||||
core.run_last()
|
|
||||||
|
|
||||||
local out_buf = vim.api.nvim_get_current_buf()
|
|
||||||
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
|
||||||
local has_raw = false
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
if line == json_line then
|
|
||||||
has_raw = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
assert.is_false(has_raw)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("reruns last JS command", function()
|
|
||||||
local calls, orig_jobstart = capture_jobstart()
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/foo_last.test.ts", "typescript", {
|
|
||||||
'describe("outer", function() {',
|
|
||||||
' it("inner 1", function() {',
|
|
||||||
" -- inside 1",
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' it("inner 2", function() {',
|
|
||||||
" -- inside 2",
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
core.run_last()
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
|
||||||
assert.are.same(
|
|
||||||
{ "npx", "jest", "--json", "--verbose", "/tmp/project/foo_last.test.ts", "-t", "inner 2" },
|
|
||||||
calls[1].cmd
|
|
||||||
)
|
|
||||||
assert.are.same(calls[1].cmd, calls[2].cmd)
|
|
||||||
assert.equals(calls[1].opts.cwd, calls[2].opts.cwd)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,97 +0,0 @@
|
|||||||
local test_samurai = require("test-samurai")
|
|
||||||
local core = require("test-samurai.core")
|
|
||||||
|
|
||||||
local function close_output_container()
|
|
||||||
local keys = vim.api.nvim_replace_termcodes("<esc><esc>", true, false, true)
|
|
||||||
local attempts = 5
|
|
||||||
while attempts > 0 do
|
|
||||||
local float_win = nil
|
|
||||||
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
||||||
local cfg = vim.api.nvim_win_get_config(win)
|
|
||||||
if cfg.relative ~= "" then
|
|
||||||
float_win = win
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not float_win then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
vim.api.nvim_set_current_win(float_win)
|
|
||||||
vim.api.nvim_feedkeys(keys, "x", false)
|
|
||||||
vim.wait(20, function()
|
|
||||||
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
||||||
local cfg = vim.api.nvim_win_get_config(win)
|
|
||||||
if cfg.relative ~= "" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end)
|
|
||||||
attempts = attempts - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function stub_jobstart(opts_config)
|
|
||||||
local orig = vim.fn.jobstart
|
|
||||||
local config = opts_config or {}
|
|
||||||
vim.fn.jobstart = function(_cmd, opts)
|
|
||||||
local out = config.stdout or nil
|
|
||||||
if out and opts and opts.on_stdout then
|
|
||||||
if type(out) == "string" then
|
|
||||||
out = { out }
|
|
||||||
end
|
|
||||||
opts.on_stdout(1, out, nil)
|
|
||||||
end
|
|
||||||
if opts and opts.on_exit then
|
|
||||||
opts.on_exit(1, config.exit_code or 0, nil)
|
|
||||||
end
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
return orig
|
|
||||||
end
|
|
||||||
|
|
||||||
describe("test-samurai quickfix (js)", function()
|
|
||||||
before_each(function()
|
|
||||||
test_samurai.setup()
|
|
||||||
end)
|
|
||||||
|
|
||||||
after_each(function()
|
|
||||||
close_output_container()
|
|
||||||
vim.fn.setqflist({}, "r")
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("mappt jest-verbose Failures auf die Zeile des Tests", function()
|
|
||||||
local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_js")
|
|
||||||
vim.fn.mkdir(root, "p")
|
|
||||||
local path = root .. "/foo_qf.test.ts"
|
|
||||||
vim.fn.writefile({
|
|
||||||
'describe("outer", function() {',
|
|
||||||
' it("inner 1", function() {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' it("inner 2", function() {',
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
}, path)
|
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = "typescript"
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
|
|
||||||
local fail_symbol = string.char(0xE2, 0x9C, 0x95)
|
|
||||||
local orig_jobstart = stub_jobstart({
|
|
||||||
exit_code = 1,
|
|
||||||
stdout = { " " .. fail_symbol .. " inner 2" },
|
|
||||||
})
|
|
||||||
|
|
||||||
core.run_file()
|
|
||||||
|
|
||||||
local qf = vim.fn.getqflist()
|
|
||||||
assert.equals(1, #qf)
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[1].bufnr))
|
|
||||||
assert.equals(5, qf[1].lnum)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
@@ -1,409 +0,0 @@
|
|||||||
local test_samurai = require("test-samurai")
|
|
||||||
local core = require("test-samurai.core")
|
|
||||||
|
|
||||||
local function close_output_container()
|
|
||||||
local keys = vim.api.nvim_replace_termcodes("<esc><esc>", true, false, true)
|
|
||||||
local attempts = 5
|
|
||||||
while attempts > 0 do
|
|
||||||
local float_win = nil
|
|
||||||
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
||||||
local cfg = vim.api.nvim_win_get_config(win)
|
|
||||||
if cfg.relative ~= "" then
|
|
||||||
float_win = win
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not float_win then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
vim.api.nvim_set_current_win(float_win)
|
|
||||||
vim.api.nvim_feedkeys(keys, "x", false)
|
|
||||||
vim.wait(20, function()
|
|
||||||
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
||||||
local cfg = vim.api.nvim_win_get_config(win)
|
|
||||||
if cfg.relative ~= "" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end)
|
|
||||||
attempts = attempts - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function stub_jobstart(opts_config)
|
|
||||||
local orig = vim.fn.jobstart
|
|
||||||
local idx = 0
|
|
||||||
local config = opts_config or {}
|
|
||||||
vim.fn.jobstart = function(cmd, opts)
|
|
||||||
idx = idx + 1
|
|
||||||
local code = 0
|
|
||||||
if type(config.exit_codes) == "table" then
|
|
||||||
code = config.exit_codes[idx] or 0
|
|
||||||
elseif type(config.exit_codes) == "number" then
|
|
||||||
code = config.exit_codes
|
|
||||||
end
|
|
||||||
local out = config.stdout and config.stdout[idx] or nil
|
|
||||||
if out and opts and opts.on_stdout then
|
|
||||||
if type(out) == "string" then
|
|
||||||
out = { out }
|
|
||||||
end
|
|
||||||
opts.on_stdout(1, out, nil)
|
|
||||||
end
|
|
||||||
if opts and opts.on_exit then
|
|
||||||
opts.on_exit(1, code, nil)
|
|
||||||
end
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
return orig
|
|
||||||
end
|
|
||||||
|
|
||||||
describe("test-samurai quickfix", function()
|
|
||||||
before_each(function()
|
|
||||||
test_samurai.setup()
|
|
||||||
end)
|
|
||||||
|
|
||||||
after_each(function()
|
|
||||||
close_output_container()
|
|
||||||
vim.fn.setqflist({}, "r")
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("fuellt die Quickfix-Liste mit Fehltests und leert sie bei Erfolg", function()
|
|
||||||
local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf")
|
|
||||||
vim.fn.mkdir(root, "p")
|
|
||||||
local path = root .. "/foo_test.go"
|
|
||||||
vim.fn.writefile({
|
|
||||||
"package foo",
|
|
||||||
"",
|
|
||||||
"func TestFoo(t *testing.T) {",
|
|
||||||
' t.Run("bar", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
"",
|
|
||||||
"func TestBaz(t *testing.T) {",
|
|
||||||
"}",
|
|
||||||
}, path)
|
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = "go"
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
|
|
||||||
local orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1, 0 },
|
|
||||||
stdout = {
|
|
||||||
{
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestFoo/bar" }),
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestBaz" }),
|
|
||||||
},
|
|
||||||
{ vim.json.encode({ Action = "pass", Test = "TestFoo" }) },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
core.run_file()
|
|
||||||
|
|
||||||
local first = vim.fn.getqflist()
|
|
||||||
assert.equals(2, #first)
|
|
||||||
assert.equals(path, vim.fn.bufname(first[1].bufnr))
|
|
||||||
assert.equals(4, first[1].lnum)
|
|
||||||
assert.equals(path, vim.fn.bufname(first[2].bufnr))
|
|
||||||
assert.equals(8, first[2].lnum)
|
|
||||||
|
|
||||||
close_output_container()
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
|
|
||||||
core.run_file()
|
|
||||||
|
|
||||||
local second = vim.fn.getqflist()
|
|
||||||
assert.equals(0, #second)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("enthaelt bei Go auch Eltern- und Subtest-Failures im Quickfix", function()
|
|
||||||
local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_go_sub")
|
|
||||||
vim.fn.mkdir(root, "p")
|
|
||||||
local path = root .. "/foo_sub_test.go"
|
|
||||||
vim.fn.writefile({
|
|
||||||
"package foo",
|
|
||||||
"",
|
|
||||||
"func TestAwesomeThing(t *testing.T) {",
|
|
||||||
' t.Run("evergreen", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' t.Run("everred", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
}, path)
|
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = "go"
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
|
|
||||||
"package foo",
|
|
||||||
"",
|
|
||||||
"func TestAwesomeThing(t *testing.T) {",
|
|
||||||
' t.Run("evergreen", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' t.Run("everred", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
})
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 6, 0 })
|
|
||||||
|
|
||||||
local orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1 },
|
|
||||||
stdout = {
|
|
||||||
{
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestAwesomeThing/everred" }),
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestAwesomeThing" }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
|
|
||||||
local qf = vim.fn.getqflist()
|
|
||||||
assert.equals(2, #qf)
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[1].bufnr))
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[2].bufnr))
|
|
||||||
local lines = { qf[1].lnum, qf[2].lnum }
|
|
||||||
table.sort(lines)
|
|
||||||
assert.are.same({ 3, 7 }, lines)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("vereinigt Failures aus Parser und Scope fuer Go", function()
|
|
||||||
local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_go_union")
|
|
||||||
vim.fn.mkdir(root, "p")
|
|
||||||
local path = root .. "/foo_union_test.go"
|
|
||||||
vim.fn.writefile({
|
|
||||||
"package foo",
|
|
||||||
"",
|
|
||||||
"func TestAwesomeThing(t *testing.T) {",
|
|
||||||
' t.Run(\"evergreen\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' t.Run(\"everred\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
}, path)
|
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = "go"
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
|
|
||||||
"package foo",
|
|
||||||
"",
|
|
||||||
"func TestAwesomeThing(t *testing.T) {",
|
|
||||||
' t.Run(\"evergreen\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' t.Run(\"everred\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
})
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 6, 0 })
|
|
||||||
|
|
||||||
local go = require("test-samurai.runners.go")
|
|
||||||
local orig_parser = go.output_parser
|
|
||||||
go.output_parser = function()
|
|
||||||
local seen = 0
|
|
||||||
return {
|
|
||||||
on_line = function()
|
|
||||||
seen = seen + 1
|
|
||||||
if seen == 1 then
|
|
||||||
return {
|
|
||||||
passes = {},
|
|
||||||
failures = { "TestAwesomeThing/everred" },
|
|
||||||
skips = {},
|
|
||||||
display = { passes = {}, failures = { "everred" }, skips = {} },
|
|
||||||
failures_all = { "TestAwesomeThing" },
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return {
|
|
||||||
passes = {},
|
|
||||||
failures = { "TestAwesomeThing" },
|
|
||||||
skips = {},
|
|
||||||
display = { passes = {}, failures = { "TestAwesomeThing" }, skips = {} },
|
|
||||||
failures_all = { "TestAwesomeThing" },
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
on_complete = function()
|
|
||||||
return nil
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1 },
|
|
||||||
stdout = {
|
|
||||||
{
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestAwesomeThing/everred" }),
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestAwesomeThing" }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
|
|
||||||
local qf = vim.fn.getqflist()
|
|
||||||
assert.equals(2, #qf)
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[1].bufnr))
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[2].bufnr))
|
|
||||||
local lines = { qf[1].lnum, qf[2].lnum }
|
|
||||||
table.sort(lines)
|
|
||||||
assert.are.same({ 3, 7 }, lines)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
go.output_parser = orig_parser
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("nutzt Listing-Namen wenn Parser keine Failure-Liste liefert", function()
|
|
||||||
local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_go_listing")
|
|
||||||
vim.fn.mkdir(root, "p")
|
|
||||||
local path = root .. "/foo_listing_test.go"
|
|
||||||
vim.fn.writefile({
|
|
||||||
"package foo",
|
|
||||||
"",
|
|
||||||
"func TestAwesomeThing(t *testing.T) {",
|
|
||||||
' t.Run(\"evergreen\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' t.Run(\"everred\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
}, path)
|
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = "go"
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
|
|
||||||
"package foo",
|
|
||||||
"",
|
|
||||||
"func TestAwesomeThing(t *testing.T) {",
|
|
||||||
' t.Run(\"evergreen\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' t.Run(\"everred\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
})
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 6, 0 })
|
|
||||||
|
|
||||||
local go = require("test-samurai.runners.go")
|
|
||||||
local orig_parser = go.output_parser
|
|
||||||
go.output_parser = function()
|
|
||||||
local step = 0
|
|
||||||
return {
|
|
||||||
on_line = function()
|
|
||||||
step = step + 1
|
|
||||||
if step == 1 then
|
|
||||||
return {
|
|
||||||
passes = {},
|
|
||||||
failures = {},
|
|
||||||
skips = {},
|
|
||||||
display = { passes = {}, failures = { "TestAwesomeThing" }, skips = {} },
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return {
|
|
||||||
passes = {},
|
|
||||||
failures = {},
|
|
||||||
skips = {},
|
|
||||||
display = { passes = {}, failures = { "everred" }, skips = {} },
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
on_complete = function()
|
|
||||||
return nil
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
local orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1 },
|
|
||||||
stdout = {
|
|
||||||
{
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestAwesomeThing/everred" }),
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestAwesomeThing" }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
|
|
||||||
local qf = vim.fn.getqflist()
|
|
||||||
assert.equals(2, #qf)
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[1].bufnr))
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[2].bufnr))
|
|
||||||
local lines = { qf[1].lnum, qf[2].lnum }
|
|
||||||
table.sort(lines)
|
|
||||||
assert.are.same({ 3, 7 }, lines)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
go.output_parser = orig_parser
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("mappt Go-Subtests mit durch Unterstriche normalisierten Namen", function()
|
|
||||||
local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_go_norm")
|
|
||||||
vim.fn.mkdir(root, "p")
|
|
||||||
local path = root .. "/foo_norm_test.go"
|
|
||||||
vim.fn.writefile({
|
|
||||||
"package foo",
|
|
||||||
"",
|
|
||||||
"func TestHandleGet(t *testing.T) {",
|
|
||||||
' t.Run(\"returns 200 with an list of all badges\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' t.Run(\"returns 500 on any db error\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
}, path)
|
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = "go"
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
|
|
||||||
"package foo",
|
|
||||||
"",
|
|
||||||
"func TestHandleGet(t *testing.T) {",
|
|
||||||
' t.Run(\"returns 200 with an list of all badges\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' t.Run(\"returns 500 on any db error\", func(t *testing.T) {',
|
|
||||||
" })",
|
|
||||||
"}",
|
|
||||||
})
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 6, 0 })
|
|
||||||
|
|
||||||
local orig_jobstart = stub_jobstart({
|
|
||||||
exit_codes = { 1 },
|
|
||||||
stdout = {
|
|
||||||
{
|
|
||||||
vim.json.encode({
|
|
||||||
Action = "fail",
|
|
||||||
Test = "TestHandleGet/returns_500_on_any_db_error",
|
|
||||||
}),
|
|
||||||
vim.json.encode({ Action = "fail", Test = "TestHandleGet" }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
|
|
||||||
local qf = vim.fn.getqflist()
|
|
||||||
assert.equals(2, #qf)
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[1].bufnr))
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[2].bufnr))
|
|
||||||
local lines = { qf[1].lnum, qf[2].lnum }
|
|
||||||
table.sort(lines)
|
|
||||||
assert.are.same({ 3, 7 }, lines)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
local test_samurai = require("test-samurai")
|
|
||||||
local core = require("test-samurai.core")
|
|
||||||
|
|
||||||
local function close_output_container()
|
|
||||||
local keys = vim.api.nvim_replace_termcodes("<esc><esc>", true, false, true)
|
|
||||||
local attempts = 5
|
|
||||||
while attempts > 0 do
|
|
||||||
local float_win = nil
|
|
||||||
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
||||||
local cfg = vim.api.nvim_win_get_config(win)
|
|
||||||
if cfg.relative ~= "" then
|
|
||||||
float_win = win
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not float_win then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
vim.api.nvim_set_current_win(float_win)
|
|
||||||
vim.api.nvim_feedkeys(keys, "x", false)
|
|
||||||
vim.wait(20, function()
|
|
||||||
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
|
||||||
local cfg = vim.api.nvim_win_get_config(win)
|
|
||||||
if cfg.relative ~= "" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end)
|
|
||||||
attempts = attempts - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function stub_jobstart(opts_config)
|
|
||||||
local orig = vim.fn.jobstart
|
|
||||||
local config = opts_config or {}
|
|
||||||
vim.fn.jobstart = function(_cmd, opts)
|
|
||||||
local out = config.stdout or nil
|
|
||||||
if out and opts and opts.on_stdout then
|
|
||||||
if type(out) == "string" then
|
|
||||||
out = { out }
|
|
||||||
end
|
|
||||||
opts.on_stdout(1, out, nil)
|
|
||||||
end
|
|
||||||
if opts and opts.on_exit then
|
|
||||||
opts.on_exit(1, config.exit_code or 0, nil)
|
|
||||||
end
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
return orig
|
|
||||||
end
|
|
||||||
|
|
||||||
describe("test-samurai quickfix (vitest)", function()
|
|
||||||
before_each(function()
|
|
||||||
test_samurai.setup()
|
|
||||||
end)
|
|
||||||
|
|
||||||
after_each(function()
|
|
||||||
close_output_container()
|
|
||||||
vim.fn.setqflist({}, "r")
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("mappt tap-flat Failures mit >-Trenner auf die Testzeile", function()
|
|
||||||
local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_vitest")
|
|
||||||
vim.fn.mkdir(root, "p")
|
|
||||||
local path = root .. "/foo_qf.test.ts"
|
|
||||||
local pkg = root .. "/package.json"
|
|
||||||
vim.fn.writefile({
|
|
||||||
"{",
|
|
||||||
' "devDependencies": { "vitest": "^1.0.0" }',
|
|
||||||
"}",
|
|
||||||
}, pkg)
|
|
||||||
vim.fn.writefile({
|
|
||||||
'describe("outer", function() {',
|
|
||||||
' it("inner 1", function() {',
|
|
||||||
" })",
|
|
||||||
"",
|
|
||||||
' it("inner 2", function() {',
|
|
||||||
" })",
|
|
||||||
"})",
|
|
||||||
}, path)
|
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = "typescript"
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
|
|
||||||
local orig_jobstart = stub_jobstart({
|
|
||||||
exit_code = 1,
|
|
||||||
stdout = { "not ok 1 - outer > inner 2 # time=12.3ms" },
|
|
||||||
})
|
|
||||||
|
|
||||||
core.run_file()
|
|
||||||
|
|
||||||
local qf = vim.fn.getqflist()
|
|
||||||
assert.equals(1, #qf)
|
|
||||||
assert.equals(path, vim.fn.bufname(qf[1].bufnr))
|
|
||||||
assert.equals(5, qf[1].lnum)
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{ "name": "mocha-jump" }
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
describe("suite one", function() {
|
|
||||||
it("test one", function() {
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
describe("suite two", function() {
|
|
||||||
it("test two", function() {
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package foo
|
|
||||||
|
|
||||||
func TestFoo(t *testing.T) {
|
|
||||||
t.Run("bar", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBaz(t *testing.T) {
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package foo
|
|
||||||
|
|
||||||
func TestAwesomeThing(t *testing.T) {
|
|
||||||
t.Run("evergreen", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("everred", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package foo
|
|
||||||
|
|
||||||
func TestHandleGet(t *testing.T) {
|
|
||||||
t.Run("returns 200 with an list of all badges", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("returns 500 on any db error", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package foo
|
|
||||||
|
|
||||||
func TestAwesomeThing(t *testing.T) {
|
|
||||||
t.Run("evergreen", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("everred", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package foo
|
|
||||||
|
|
||||||
func TestAwesomeThing(t *testing.T) {
|
|
||||||
t.Run("evergreen", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("everred", func(t *testing.T) {
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
describe("outer", function() {
|
|
||||||
it("inner 1", function() {
|
|
||||||
})
|
|
||||||
|
|
||||||
it("inner 2", function() {
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package foo
|
|
||||||
|
|
||||||
func TestFoo(t *testing.T) {}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package foo
|
|
||||||
|
|
||||||
func TestFoo(t *testing.T) {
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package foo
|
|
||||||
|
|
||||||
func TestFoo(t *testing.T) {
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
package foo
|
|
||||||
|
|
||||||
func TestFoo(t *testing.T) {
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
describe("outer", function() {
|
|
||||||
it("inner 1", function() {
|
|
||||||
})
|
|
||||||
|
|
||||||
it("inner 2", function() {
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"devDependencies": { "vitest": "^1.0.0" }
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user