Compare commits
17 Commits
77a7ebab4d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
456a157549
|
|||
|
8c598002e4
|
|||
|
bd6930adc0
|
|||
|
24bd89a7be
|
|||
|
fd3952bedf
|
|||
|
238d5f9634
|
|||
|
118f84c31e
|
|||
|
924584d8b3
|
|||
|
5d0b4e9dd6
|
|||
|
c538a32307
|
|||
|
e9a7d2029b
|
|||
|
4c2d585f2d
|
|||
|
e315a8e8f2
|
|||
|
1d9b682a58
|
|||
|
6505a91cce
|
|||
|
f5fc9822ce
|
|||
|
58f0edc14b
|
@@ -29,5 +29,13 @@ jobs:
|
|||||||
- name: Neovim Version
|
- name: Neovim Version
|
||||||
run: nvim --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
|
- name: Run Tests
|
||||||
run: bash run_test.sh
|
run: bash run_test.sh
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ local state = {
|
|||||||
detail_win = nil,
|
detail_win = nil,
|
||||||
detail_opening = false,
|
detail_opening = false,
|
||||||
detail_full = false,
|
detail_full = false,
|
||||||
|
trigger_win = nil,
|
||||||
|
trigger_buf = nil,
|
||||||
|
trigger_cursor = nil,
|
||||||
|
listing_unfiltered_lines = nil,
|
||||||
|
listing_filtered_kind = nil,
|
||||||
|
detail_line = nil,
|
||||||
hardtime_refcount = 0,
|
hardtime_refcount = 0,
|
||||||
hardtime_was_enabled = false,
|
hardtime_was_enabled = false,
|
||||||
autocmds_set = false,
|
autocmds_set = false,
|
||||||
@@ -31,11 +37,16 @@ local state = {
|
|||||||
local summary_ns = vim.api.nvim_create_namespace("TestSamuraiSummary")
|
local summary_ns = vim.api.nvim_create_namespace("TestSamuraiSummary")
|
||||||
local result_ns = vim.api.nvim_create_namespace("TestSamuraiResult")
|
local result_ns = vim.api.nvim_create_namespace("TestSamuraiResult")
|
||||||
local detail_ns = vim.api.nvim_create_namespace("TestSamuraiDetailAnsi")
|
local detail_ns = vim.api.nvim_create_namespace("TestSamuraiDetailAnsi")
|
||||||
|
local help_ns = vim.api.nvim_create_namespace("TestSamuraiHelp")
|
||||||
|
local listing_sel_ns = vim.api.nvim_create_namespace("TestSamuraiListingSelection")
|
||||||
local apply_border_kind
|
local apply_border_kind
|
||||||
local close_container
|
local close_container
|
||||||
local restore_listing_full
|
local restore_listing_full
|
||||||
local close_detail_float
|
local close_detail_float
|
||||||
local jump_to_first_quickfix
|
local jump_to_first_quickfix
|
||||||
|
local apply_summary_highlights
|
||||||
|
local apply_result_highlights
|
||||||
|
local run_command
|
||||||
|
|
||||||
local function disable_container_maps(buf)
|
local function disable_container_maps(buf)
|
||||||
local opts = { buffer = buf, nowait = true, silent = true }
|
local opts = { buffer = buf, nowait = true, silent = true }
|
||||||
@@ -43,6 +54,170 @@ local function disable_container_maps(buf)
|
|||||||
vim.keymap.set("n", "<C-k>", "<Nop>", opts)
|
vim.keymap.set("n", "<C-k>", "<Nop>", opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function help_lines()
|
||||||
|
return {
|
||||||
|
"Test-Samurai Help",
|
||||||
|
"",
|
||||||
|
"TSam commands:",
|
||||||
|
" TSamNearest <leader>tn",
|
||||||
|
" TSamFile <leader>tf",
|
||||||
|
" TSamAll <leader>ta",
|
||||||
|
" TSamLast <leader>tl",
|
||||||
|
" TSamFailedOnly <leader>te",
|
||||||
|
" TSamShowOutput <leader>to",
|
||||||
|
"",
|
||||||
|
"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 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",
|
||||||
|
" <leader>cb breaks test-command onto multiple lines (clears search highlight)",
|
||||||
|
" <leader>cj joins test-command onto single line",
|
||||||
|
"",
|
||||||
|
"Testing-Float (Listing):",
|
||||||
|
" <cr> Open Detail-Float for selected test",
|
||||||
|
" <esc><esc> Close Testing-Float and restore cursor",
|
||||||
|
" <C-l> Focus Detail-Float (press l again for full)",
|
||||||
|
" <C-h> Focus Test-Listing-Float",
|
||||||
|
" <leader>z Toggle Detail-Float full width",
|
||||||
|
" ? Show this help",
|
||||||
|
"",
|
||||||
|
"Testing-Float (Detail):",
|
||||||
|
" <esc><esc> Close Testing-Float and restore cursor",
|
||||||
|
" <C-h> Focus Test-Listing-Float",
|
||||||
|
" <C-w>h Focus Test-Listing-Float",
|
||||||
|
" <C-l> Focus Detail-Float",
|
||||||
|
" <leader>z Toggle Detail-Float full width",
|
||||||
|
" <C-c> Close Detail-Float",
|
||||||
|
" ? Show this help",
|
||||||
|
"",
|
||||||
|
"Notes:",
|
||||||
|
" No output captured -> shows placeholder text",
|
||||||
|
" Active listing entry is highlighted while Detail-Float is visible",
|
||||||
|
" Buffers are saved via :wall before every test run",
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function split_listing_sections(lines)
|
||||||
|
local summary_start = nil
|
||||||
|
for i, line in ipairs(lines or {}) do
|
||||||
|
if line:match("^TOTAL%s+%d+") then
|
||||||
|
summary_start = i - 1
|
||||||
|
if summary_start < 2 then
|
||||||
|
summary_start = 2
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local header = {}
|
||||||
|
local body = {}
|
||||||
|
local summary = {}
|
||||||
|
if lines and #lines > 0 then
|
||||||
|
header = { lines[1] }
|
||||||
|
end
|
||||||
|
for i, line in ipairs(lines or {}) do
|
||||||
|
if i == 1 then
|
||||||
|
elseif summary_start and i >= summary_start then
|
||||||
|
table.insert(summary, line)
|
||||||
|
else
|
||||||
|
table.insert(body, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return header, body, summary
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rebuild_result_line_map(lines)
|
||||||
|
state.last_result_line_map = {}
|
||||||
|
for idx, line in ipairs(lines or {}) do
|
||||||
|
local status = line:match("^%[%s*(%u+)%s*%]%s*%-")
|
||||||
|
if status == "PASS" or status == "FAIL" or status == "SKIP" then
|
||||||
|
local name = line:match("^%[%s*[%u]+%s*%]%s*%-%s*(.+)$")
|
||||||
|
if name and name ~= "" then
|
||||||
|
state.last_result_line_map[idx] = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply_listing_lines(buf, lines)
|
||||||
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||||
|
vim.api.nvim_buf_clear_namespace(buf, result_ns, 0, -1)
|
||||||
|
vim.api.nvim_buf_clear_namespace(buf, summary_ns, 0, -1)
|
||||||
|
vim.api.nvim_buf_clear_namespace(buf, listing_sel_ns, 0, -1)
|
||||||
|
apply_result_highlights(buf, 0, lines)
|
||||||
|
apply_summary_highlights(buf, 0, lines)
|
||||||
|
rebuild_result_line_map(lines)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply_listing_substitution(command)
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_call(buf, function()
|
||||||
|
vim.cmd(command)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply_listing_filter(kind)
|
||||||
|
if not (state.last_buf and vim.api.nvim_buf_is_valid(state.last_buf)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if kind == "all" then
|
||||||
|
if not state.listing_filtered_kind or not state.listing_unfiltered_lines then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
apply_listing_lines(state.last_buf, state.listing_unfiltered_lines)
|
||||||
|
state.listing_filtered_kind = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if state.listing_filtered_kind == kind then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local base = state.listing_unfiltered_lines
|
||||||
|
if not base then
|
||||||
|
base = vim.api.nvim_buf_get_lines(state.last_buf, 0, -1, false)
|
||||||
|
state.listing_unfiltered_lines = vim.deepcopy(base)
|
||||||
|
end
|
||||||
|
local header, body, summary = split_listing_sections(base)
|
||||||
|
local filtered = {}
|
||||||
|
for _, line in ipairs(body) do
|
||||||
|
if kind == "fail" and line:match("^%[ FAIL %] %-") then
|
||||||
|
table.insert(filtered, line)
|
||||||
|
elseif kind == "skip" and line:match("^%[ SKIP %] %-") then
|
||||||
|
table.insert(filtered, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #filtered == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local combined = {}
|
||||||
|
if #header > 0 then
|
||||||
|
table.insert(combined, header[1])
|
||||||
|
table.insert(combined, "")
|
||||||
|
end
|
||||||
|
for _, line in ipairs(filtered) do
|
||||||
|
table.insert(combined, line)
|
||||||
|
end
|
||||||
|
for _, line in ipairs(summary) do
|
||||||
|
table.insert(combined, line)
|
||||||
|
end
|
||||||
|
apply_listing_lines(state.last_buf, combined)
|
||||||
|
state.listing_filtered_kind = kind
|
||||||
|
end
|
||||||
|
|
||||||
local function get_hardtime()
|
local function get_hardtime()
|
||||||
local ok, hardtime = pcall(require, "hardtime")
|
local ok, hardtime = pcall(require, "hardtime")
|
||||||
if not ok or type(hardtime) ~= "table" then
|
if not ok or type(hardtime) ~= "table" then
|
||||||
@@ -107,11 +282,11 @@ local function jump_listing_fail(direction)
|
|||||||
local win = vim.api.nvim_get_current_win()
|
local win = vim.api.nvim_get_current_win()
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
||||||
return
|
return nil
|
||||||
end
|
end
|
||||||
local total = vim.api.nvim_buf_line_count(buf)
|
local total = vim.api.nvim_buf_line_count(buf)
|
||||||
if total == 0 then
|
if total == 0 then
|
||||||
return
|
return nil
|
||||||
end
|
end
|
||||||
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||||
local cursor = vim.api.nvim_win_get_cursor(win)
|
local cursor = vim.api.nvim_win_get_cursor(win)
|
||||||
@@ -163,6 +338,76 @@ local function jump_listing_fail(direction)
|
|||||||
if target then
|
if target then
|
||||||
vim.api.nvim_win_set_cursor(win, { target, 0 })
|
vim.api.nvim_win_set_cursor(win, { target, 0 })
|
||||||
end
|
end
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
|
||||||
|
local function jump_to_first_listing_entry()
|
||||||
|
local win = vim.api.nvim_get_current_win()
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local total = vim.api.nvim_buf_line_count(buf)
|
||||||
|
if total == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||||
|
for i = 1, total do
|
||||||
|
if lines[i] and lines[i]:match("^%[ %u+ %] %- ") then
|
||||||
|
vim.api.nvim_win_set_cursor(win, { i, 0 })
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function jump_listing_and_open(kind)
|
||||||
|
local current_win = vim.api.nvim_get_current_win()
|
||||||
|
local listing_win = state.last_win
|
||||||
|
if listing_win and vim.api.nvim_win_is_valid(listing_win) then
|
||||||
|
vim.api.nvim_set_current_win(listing_win)
|
||||||
|
else
|
||||||
|
listing_win = current_win
|
||||||
|
end
|
||||||
|
local target = nil
|
||||||
|
if kind == "next" then
|
||||||
|
target = jump_listing_fail("next")
|
||||||
|
elseif kind == "prev" then
|
||||||
|
target = jump_listing_fail("prev")
|
||||||
|
elseif kind == "first" then
|
||||||
|
target = jump_to_first_listing_entry()
|
||||||
|
end
|
||||||
|
if target then
|
||||||
|
M.open_test_output_at_cursor()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if current_win and vim.api.nvim_win_is_valid(current_win) then
|
||||||
|
vim.api.nvim_set_current_win(current_win)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function clear_listing_selection()
|
||||||
|
if state.last_buf and vim.api.nvim_buf_is_valid(state.last_buf) then
|
||||||
|
vim.api.nvim_buf_clear_namespace(state.last_buf, listing_sel_ns, 0, -1)
|
||||||
|
end
|
||||||
|
state.detail_line = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply_listing_selection(line)
|
||||||
|
if not (state.last_buf and vim.api.nvim_buf_is_valid(state.last_buf)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_clear_namespace(state.last_buf, listing_sel_ns, 0, -1)
|
||||||
|
if not line then
|
||||||
|
state.detail_line = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local total = vim.api.nvim_buf_line_count(state.last_buf)
|
||||||
|
if line < 1 or line > total then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_add_highlight(state.last_buf, listing_sel_ns, "TestSamuraiListingActive", line - 1, 0, -1)
|
||||||
|
state.detail_line = line
|
||||||
end
|
end
|
||||||
|
|
||||||
local function find_normal_window()
|
local function find_normal_window()
|
||||||
@@ -175,6 +420,54 @@ local function find_normal_window()
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function capture_trigger_location()
|
||||||
|
local win = vim.api.nvim_get_current_win()
|
||||||
|
local cfg = vim.api.nvim_win_get_config(win)
|
||||||
|
if cfg.relative ~= "" then
|
||||||
|
win = find_normal_window()
|
||||||
|
end
|
||||||
|
if not (win and vim.api.nvim_win_is_valid(win)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local buf = vim.api.nvim_win_get_buf(win)
|
||||||
|
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local cursor = vim.api.nvim_win_get_cursor(win)
|
||||||
|
state.trigger_win = win
|
||||||
|
state.trigger_buf = buf
|
||||||
|
state.trigger_cursor = { cursor[1], cursor[2] }
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restore_trigger_location()
|
||||||
|
local buf = state.trigger_buf
|
||||||
|
local cursor = state.trigger_cursor
|
||||||
|
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local target_win = nil
|
||||||
|
if state.trigger_win and vim.api.nvim_win_is_valid(state.trigger_win) then
|
||||||
|
local cfg = vim.api.nvim_win_get_config(state.trigger_win)
|
||||||
|
if cfg.relative == "" then
|
||||||
|
target_win = state.trigger_win
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not target_win then
|
||||||
|
target_win = find_normal_window()
|
||||||
|
end
|
||||||
|
if not (target_win and vim.api.nvim_win_is_valid(target_win)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.api.nvim_set_current_win(target_win)
|
||||||
|
vim.api.nvim_win_set_buf(target_win, buf)
|
||||||
|
if cursor then
|
||||||
|
pcall(vim.api.nvim_win_set_cursor, target_win, cursor)
|
||||||
|
end
|
||||||
|
state.trigger_win = nil
|
||||||
|
state.trigger_buf = nil
|
||||||
|
state.trigger_cursor = nil
|
||||||
|
end
|
||||||
|
|
||||||
local function jump_to_listing_test()
|
local function jump_to_listing_test()
|
||||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||||
local line = cursor[1]
|
local line = cursor[1]
|
||||||
@@ -269,6 +562,7 @@ local function setup_summary_highlights()
|
|||||||
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiResultSkip", { fg = skip_fg })
|
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiResultSkip", { fg = skip_fg })
|
||||||
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiBorderPass", { fg = pass_fg, bold = true })
|
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiBorderPass", { fg = pass_fg, bold = true })
|
||||||
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiBorderFail", { fg = fail_fg, bold = true })
|
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiBorderFail", { fg = fail_fg, bold = true })
|
||||||
|
pcall(vim.api.nvim_set_hl, 0, "TestSamuraiListingActive", { link = "Visual" })
|
||||||
end
|
end
|
||||||
|
|
||||||
local function load_runners()
|
local function load_runners()
|
||||||
@@ -302,6 +596,7 @@ local function ensure_output_autocmds()
|
|||||||
if state.detail_win and closed == state.detail_win then
|
if state.detail_win and closed == state.detail_win then
|
||||||
state.detail_win = nil
|
state.detail_win = nil
|
||||||
restore_listing_full()
|
restore_listing_full()
|
||||||
|
clear_listing_selection()
|
||||||
hardtime_restore()
|
hardtime_restore()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -312,6 +607,7 @@ local function ensure_output_autocmds()
|
|||||||
pcall(vim.api.nvim_win_close, state.detail_win, true)
|
pcall(vim.api.nvim_win_close, state.detail_win, true)
|
||||||
state.detail_win = nil
|
state.detail_win = nil
|
||||||
end
|
end
|
||||||
|
clear_listing_selection()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@@ -436,13 +732,6 @@ function M.get_runner_for_buf(bufnr)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if path:find(".test.", 1, true) or path:find(".spec.", 1, true) then
|
|
||||||
local ok, jsjest = pcall(require, "test-samurai.runners.js-jest")
|
|
||||||
if ok and type(jsjest) == "table" then
|
|
||||||
return jsjest
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -486,6 +775,12 @@ close_container = function()
|
|||||||
pcall(vim.api.nvim_win_close, state.last_win, true)
|
pcall(vim.api.nvim_win_close, state.last_win, true)
|
||||||
state.last_win = nil
|
state.last_win = nil
|
||||||
end
|
end
|
||||||
|
clear_listing_selection()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function close_container_and_restore()
|
||||||
|
close_container()
|
||||||
|
restore_trigger_location()
|
||||||
end
|
end
|
||||||
|
|
||||||
jump_to_first_quickfix = function()
|
jump_to_first_quickfix = function()
|
||||||
@@ -500,6 +795,7 @@ close_detail_float = function()
|
|||||||
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
|
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
|
||||||
pcall(vim.api.nvim_win_close, state.detail_win, true)
|
pcall(vim.api.nvim_win_close, state.detail_win, true)
|
||||||
end
|
end
|
||||||
|
clear_listing_selection()
|
||||||
end
|
end
|
||||||
|
|
||||||
restore_listing_full = function()
|
restore_listing_full = function()
|
||||||
@@ -573,6 +869,7 @@ local function apply_split_layout(left_ratio)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function create_output_win(initial_lines)
|
local function create_output_win(initial_lines)
|
||||||
|
capture_trigger_location()
|
||||||
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
|
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
|
||||||
pcall(vim.api.nvim_win_close, state.detail_win, true)
|
pcall(vim.api.nvim_win_close, state.detail_win, true)
|
||||||
state.detail_win = nil
|
state.detail_win = nil
|
||||||
@@ -607,7 +904,7 @@ local function create_output_win(initial_lines)
|
|||||||
hardtime_disable()
|
hardtime_disable()
|
||||||
|
|
||||||
vim.keymap.set("n", "<esc><esc>", function()
|
vim.keymap.set("n", "<esc><esc>", function()
|
||||||
close_container()
|
close_container_and_restore()
|
||||||
end, { buffer = buf, nowait = true, silent = true })
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<cr>", function()
|
vim.keymap.set("n", "<cr>", function()
|
||||||
M.open_test_output_at_cursor()
|
M.open_test_output_at_cursor()
|
||||||
@@ -621,11 +918,20 @@ local function create_output_win(initial_lines)
|
|||||||
vim.keymap.set("n", "<leader>z", function()
|
vim.keymap.set("n", "<leader>z", function()
|
||||||
M.toggle_detail_full()
|
M.toggle_detail_full()
|
||||||
end, { buffer = buf, nowait = true, silent = true })
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<leader>nf", function()
|
vim.keymap.set("n", "<leader>fn", function()
|
||||||
jump_listing_fail("next")
|
jump_listing_and_open("next")
|
||||||
end, { buffer = buf, nowait = true, silent = true })
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<leader>pf", function()
|
vim.keymap.set("n", "<leader>fp", function()
|
||||||
jump_listing_fail("prev")
|
jump_listing_and_open("prev")
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>ff", function()
|
||||||
|
jump_listing_and_open("first")
|
||||||
|
end, { buffer = buf, nowait = true, silent = true, desc = "[F]ind [F]irst list entry" })
|
||||||
|
vim.keymap.set("n", "<leader>cb", function()
|
||||||
|
M.listing_break_on_dashes()
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>cj", function()
|
||||||
|
M.listing_join_backslashes()
|
||||||
end, { buffer = buf, nowait = true, silent = true })
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<leader>o", function()
|
vim.keymap.set("n", "<leader>o", function()
|
||||||
jump_to_listing_test()
|
jump_to_listing_test()
|
||||||
@@ -633,6 +939,21 @@ local function create_output_win(initial_lines)
|
|||||||
vim.keymap.set("n", "<leader>qn", function()
|
vim.keymap.set("n", "<leader>qn", function()
|
||||||
jump_to_first_quickfix()
|
jump_to_first_quickfix()
|
||||||
end, { buffer = buf, nowait = true, silent = true })
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>sf", function()
|
||||||
|
M.filter_listing_failures()
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>ss", function()
|
||||||
|
M.filter_listing_skips()
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>sa", function()
|
||||||
|
M.filter_listing_all()
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>tt", function()
|
||||||
|
M.run_test_at_cursor()
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "?", function()
|
||||||
|
M.show_help()
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
disable_container_maps(buf)
|
disable_container_maps(buf)
|
||||||
|
|
||||||
state.last_win = listing
|
state.last_win = listing
|
||||||
@@ -656,6 +977,7 @@ local function reopen_output_win()
|
|||||||
state.detail_win = nil
|
state.detail_win = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
capture_trigger_location()
|
||||||
local width, height, row, col = float_geometry()
|
local width, height, row, col = float_geometry()
|
||||||
state.last_float = { width = width, height = height, row = row, col = col }
|
state.last_float = { width = width, height = height, row = row, col = col }
|
||||||
|
|
||||||
@@ -671,7 +993,7 @@ local function reopen_output_win()
|
|||||||
hardtime_disable()
|
hardtime_disable()
|
||||||
|
|
||||||
vim.keymap.set("n", "<esc><esc>", function()
|
vim.keymap.set("n", "<esc><esc>", function()
|
||||||
close_container()
|
close_container_and_restore()
|
||||||
end, { buffer = state.last_buf, nowait = true, silent = true })
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<cr>", function()
|
vim.keymap.set("n", "<cr>", function()
|
||||||
M.open_test_output_at_cursor()
|
M.open_test_output_at_cursor()
|
||||||
@@ -685,11 +1007,20 @@ local function reopen_output_win()
|
|||||||
vim.keymap.set("n", "<leader>z", function()
|
vim.keymap.set("n", "<leader>z", function()
|
||||||
M.toggle_detail_full()
|
M.toggle_detail_full()
|
||||||
end, { buffer = state.last_buf, nowait = true, silent = true })
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<leader>nf", function()
|
vim.keymap.set("n", "<leader>fn", function()
|
||||||
jump_listing_fail("next")
|
jump_listing_and_open("next")
|
||||||
end, { buffer = state.last_buf, nowait = true, silent = true })
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<leader>pf", function()
|
vim.keymap.set("n", "<leader>fp", function()
|
||||||
jump_listing_fail("prev")
|
jump_listing_and_open("prev")
|
||||||
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>ff", function()
|
||||||
|
jump_listing_and_open("first")
|
||||||
|
end, { buffer = state.last_buf, nowait = true, silent = true, desc = "[F]ind [F]irst list entry" })
|
||||||
|
vim.keymap.set("n", "<leader>cb", function()
|
||||||
|
M.listing_break_on_dashes()
|
||||||
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>cj", function()
|
||||||
|
M.listing_join_backslashes()
|
||||||
end, { buffer = state.last_buf, nowait = true, silent = true })
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<leader>o", function()
|
vim.keymap.set("n", "<leader>o", function()
|
||||||
jump_to_listing_test()
|
jump_to_listing_test()
|
||||||
@@ -697,6 +1028,21 @@ local function reopen_output_win()
|
|||||||
vim.keymap.set("n", "<leader>qn", function()
|
vim.keymap.set("n", "<leader>qn", function()
|
||||||
jump_to_first_quickfix()
|
jump_to_first_quickfix()
|
||||||
end, { buffer = state.last_buf, nowait = true, silent = true })
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>sf", function()
|
||||||
|
M.filter_listing_failures()
|
||||||
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>ss", function()
|
||||||
|
M.filter_listing_skips()
|
||||||
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>sa", function()
|
||||||
|
M.filter_listing_all()
|
||||||
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>tt", function()
|
||||||
|
M.run_test_at_cursor()
|
||||||
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "?", function()
|
||||||
|
M.show_help()
|
||||||
|
end, { buffer = state.last_buf, nowait = true, silent = true })
|
||||||
disable_container_maps(state.last_buf)
|
disable_container_maps(state.last_buf)
|
||||||
|
|
||||||
state.last_win = win
|
state.last_win = win
|
||||||
@@ -959,6 +1305,18 @@ local function apply_detail_highlights(buf, highlights)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function apply_help_highlights(buf, lines)
|
||||||
|
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_clear_namespace(buf, help_ns, 0, -1)
|
||||||
|
for lnum, line in ipairs(lines or {}) do
|
||||||
|
if line:match(":%s*$") then
|
||||||
|
vim.api.nvim_buf_add_highlight(buf, help_ns, "TestSamuraiSummaryPass", lnum - 1, 0, -1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function parse_go_output_from_raw(output)
|
local function parse_go_output_from_raw(output)
|
||||||
local out = {}
|
local out = {}
|
||||||
if not output or output == "" then
|
if not output or output == "" then
|
||||||
@@ -992,7 +1350,7 @@ local function ensure_detail_buf(lines)
|
|||||||
vim.api.nvim_buf_set_option(buf, "filetype", "test-samurai-output")
|
vim.api.nvim_buf_set_option(buf, "filetype", "test-samurai-output")
|
||||||
state.detail_buf = buf
|
state.detail_buf = buf
|
||||||
vim.keymap.set("n", "<esc><esc>", function()
|
vim.keymap.set("n", "<esc><esc>", function()
|
||||||
close_container()
|
close_container_and_restore()
|
||||||
end, { buffer = buf, nowait = true, silent = true })
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<C-w>h", function()
|
vim.keymap.set("n", "<C-w>h", function()
|
||||||
M.focus_listing()
|
M.focus_listing()
|
||||||
@@ -1006,16 +1364,29 @@ local function ensure_detail_buf(lines)
|
|||||||
vim.keymap.set("n", "<leader>z", function()
|
vim.keymap.set("n", "<leader>z", function()
|
||||||
M.toggle_detail_full()
|
M.toggle_detail_full()
|
||||||
end, { buffer = buf, nowait = true, silent = true })
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>fn", function()
|
||||||
|
jump_listing_and_open("next")
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>fp", function()
|
||||||
|
jump_listing_and_open("prev")
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>ff", function()
|
||||||
|
jump_listing_and_open("first")
|
||||||
|
end, { buffer = buf, nowait = true, silent = true, desc = "[F]ind [F]irst list entry" })
|
||||||
vim.keymap.set("n", "<C-c>", function()
|
vim.keymap.set("n", "<C-c>", function()
|
||||||
close_detail_float()
|
close_detail_float()
|
||||||
end, { buffer = buf, nowait = true, silent = true })
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
vim.keymap.set("n", "<leader>qn", function()
|
vim.keymap.set("n", "<leader>qn", function()
|
||||||
jump_to_first_quickfix()
|
jump_to_first_quickfix()
|
||||||
end, { buffer = buf, nowait = true, silent = true })
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
|
vim.keymap.set("n", "?", function()
|
||||||
|
M.show_help()
|
||||||
|
end, { buffer = buf, nowait = true, silent = true })
|
||||||
disable_container_maps(buf)
|
disable_container_maps(buf)
|
||||||
end
|
end
|
||||||
local clean_lines, highlights = parse_ansi_lines(normalize_output_lines(lines))
|
local clean_lines, highlights = parse_ansi_lines(normalize_output_lines(lines))
|
||||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, clean_lines)
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, clean_lines)
|
||||||
|
vim.api.nvim_buf_clear_namespace(buf, help_ns, 0, -1)
|
||||||
apply_detail_highlights(buf, highlights)
|
apply_detail_highlights(buf, highlights)
|
||||||
return buf
|
return buf
|
||||||
end
|
end
|
||||||
@@ -1133,11 +1504,109 @@ function M.open_test_output_at_cursor()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if type(output) ~= "table" or #output == 0 then
|
if type(output) ~= "table" or #output == 0 then
|
||||||
vim.notify("[test-samurai] No output captured for " .. test_name, vim.log.levels.WARN)
|
open_detail_split({ "", "No output captured" }, "default")
|
||||||
|
apply_listing_selection(line)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local border_kind = status and status:lower() or nil
|
local border_kind = status and status:lower() or nil
|
||||||
open_detail_split(output, border_kind)
|
open_detail_split(output, border_kind)
|
||||||
|
apply_listing_selection(line)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.show_help()
|
||||||
|
if not (state.last_win and vim.api.nvim_win_is_valid(state.last_win)) then
|
||||||
|
vim.notify("[test-samurai] No test output window", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local lines = help_lines()
|
||||||
|
open_detail_split(lines, "default")
|
||||||
|
clear_listing_selection()
|
||||||
|
apply_help_highlights(state.detail_buf, lines)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.filter_listing_failures()
|
||||||
|
apply_listing_filter("fail")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.filter_listing_skips()
|
||||||
|
apply_listing_filter("skip")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.filter_listing_all()
|
||||||
|
apply_listing_filter("all")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.listing_break_on_dashes()
|
||||||
|
apply_listing_substitution([[%s/--/\\\r\t--/g]])
|
||||||
|
vim.cmd("noh")
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.listing_join_backslashes()
|
||||||
|
apply_listing_substitution([[%s/\\\n\t//g]])
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.run_test_at_cursor()
|
||||||
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||||
|
local line = cursor[1]
|
||||||
|
local text = vim.api.nvim_get_current_line()
|
||||||
|
local status = text:match("^%[%s*(%u+)%s*%]%s*%-")
|
||||||
|
if status ~= "PASS" and status ~= "FAIL" and status ~= "SKIP" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local test_name = state.last_result_line_map[line]
|
||||||
|
if not test_name then
|
||||||
|
test_name = text:match("^%[%s*[%u]+%s*%]%s*%-%s*(.+)$")
|
||||||
|
end
|
||||||
|
if not test_name or test_name == "" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local runner = state.last_scope_runner or state.last_runner
|
||||||
|
if not runner or type(runner.build_command) ~= "function" then
|
||||||
|
vim.notify("[test-samurai] Runner missing methods", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local command_src = state.last_scope_command or state.last_command
|
||||||
|
if not command_src then
|
||||||
|
vim.notify("[test-samurai] No previous test command", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local spec = {
|
||||||
|
file = command_src.file,
|
||||||
|
cwd = command_src.cwd,
|
||||||
|
test_name = test_name,
|
||||||
|
full_name = test_name,
|
||||||
|
}
|
||||||
|
if runner._last_mocha_titles and type(runner._last_mocha_titles) == "table" then
|
||||||
|
spec.mocha_full_title = runner._last_mocha_titles[test_name]
|
||||||
|
end
|
||||||
|
if not spec.mocha_full_title and test_name:find("/", 1, true) then
|
||||||
|
spec.mocha_full_title = test_name:gsub("/", " ")
|
||||||
|
end
|
||||||
|
if not spec.file or spec.file == "" then
|
||||||
|
vim.notify("[test-samurai] Missing test file for rerun", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok_cmd, command = pcall(runner.build_command, spec)
|
||||||
|
if not ok_cmd or not command or type(command.cmd) ~= "table" or #command.cmd == 0 then
|
||||||
|
vim.notify("[test-samurai] Runner failed to build command", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
command.file = spec.file
|
||||||
|
|
||||||
|
local parser = runner.output_parser
|
||||||
|
if type(parser) == "function" then
|
||||||
|
parser = parser()
|
||||||
|
end
|
||||||
|
run_command(command, {
|
||||||
|
track_scope = true,
|
||||||
|
runner = runner,
|
||||||
|
scope_kind = "nearest",
|
||||||
|
output_parser = parser or runner.parse_results,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.focus_listing()
|
function M.focus_listing()
|
||||||
@@ -1532,7 +2001,7 @@ local function highlight_label_word(buf, ns, lnum, line, label, hl_group)
|
|||||||
vim.api.nvim_buf_add_highlight(buf, ns, hl_group, lnum, label_start - 1, label_start - 1 + #label)
|
vim.api.nvim_buf_add_highlight(buf, ns, hl_group, lnum, label_start - 1, label_start - 1 + #label)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function apply_summary_highlights(buf, start_line, lines)
|
apply_summary_highlights = function(buf, start_line, lines)
|
||||||
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -1554,7 +2023,7 @@ local function apply_summary_highlights(buf, start_line, lines)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function apply_result_highlights(buf, start_line, lines)
|
apply_result_highlights = function(buf, start_line, lines)
|
||||||
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -1592,11 +2061,14 @@ local function collect_failure_names_from_listing()
|
|||||||
return out
|
return out
|
||||||
end
|
end
|
||||||
|
|
||||||
local function run_command(command, opts)
|
run_command = function(command, opts)
|
||||||
local options = opts or {}
|
local options = opts or {}
|
||||||
|
vim.cmd("wall")
|
||||||
state.last_test_outputs = {}
|
state.last_test_outputs = {}
|
||||||
state.last_result_line_map = {}
|
state.last_result_line_map = {}
|
||||||
state.last_raw_output = nil
|
state.last_raw_output = nil
|
||||||
|
state.listing_unfiltered_lines = nil
|
||||||
|
state.listing_filtered_kind = nil
|
||||||
local failures = {}
|
local failures = {}
|
||||||
local failures_seen = {}
|
local failures_seen = {}
|
||||||
if command and type(command.cmd) == "table" and #command.cmd > 0 then
|
if command and type(command.cmd) == "table" and #command.cmd > 0 then
|
||||||
@@ -1642,7 +2114,10 @@ local function run_command(command, opts)
|
|||||||
skips = {},
|
skips = {},
|
||||||
}
|
}
|
||||||
local had_parsed_output = false
|
local had_parsed_output = false
|
||||||
local summary_enabled = options.scope_kind == "file" or options.scope_kind == "all" or options.scope_kind == "nearest"
|
local summary_enabled = options.scope_kind == "file"
|
||||||
|
or options.scope_kind == "all"
|
||||||
|
or options.scope_kind == "nearest"
|
||||||
|
or options.scope_kind == "last"
|
||||||
local summary = make_summary_tracker(summary_enabled)
|
local summary = make_summary_tracker(summary_enabled)
|
||||||
local result_counts = make_summary_tracker(true)
|
local result_counts = make_summary_tracker(true)
|
||||||
state.last_border_kind = "default"
|
state.last_border_kind = "default"
|
||||||
@@ -1811,7 +2286,7 @@ local function run_command(command, opts)
|
|||||||
append_lines(buf, summary_lines)
|
append_lines(buf, summary_lines)
|
||||||
apply_summary_highlights(buf, start_line, summary_lines)
|
apply_summary_highlights(buf, start_line, summary_lines)
|
||||||
end
|
end
|
||||||
append_lines(buf, { "", "[exit code] " .. tostring(code) })
|
append_lines(buf, { "" })
|
||||||
else
|
else
|
||||||
if not has_output then
|
if not has_output then
|
||||||
local cur = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
local cur = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||||
@@ -1830,13 +2305,14 @@ local function run_command(command, opts)
|
|||||||
append_lines(buf, summary_lines)
|
append_lines(buf, summary_lines)
|
||||||
apply_summary_highlights(buf, start_line, summary_lines)
|
apply_summary_highlights(buf, start_line, summary_lines)
|
||||||
end
|
end
|
||||||
append_lines(buf, { "", "[exit code] " .. tostring(code) })
|
append_lines(buf, { "" })
|
||||||
end
|
end
|
||||||
if options.track_scope then
|
if options.track_scope then
|
||||||
state.last_scope_exit_code = code
|
state.last_scope_exit_code = code
|
||||||
end
|
end
|
||||||
local items = {}
|
local items = {}
|
||||||
local failures_for_qf = failures
|
local failures_for_qf = failures
|
||||||
|
local fallback_failures = options.qf_fallback_failures
|
||||||
if options.track_scope and type(state.last_scope_failures) == "table" then
|
if options.track_scope and type(state.last_scope_failures) == "table" then
|
||||||
local merged = {}
|
local merged = {}
|
||||||
local seen = {}
|
local seen = {}
|
||||||
@@ -1872,8 +2348,20 @@ local function run_command(command, opts)
|
|||||||
end
|
end
|
||||||
failures_for_qf = merged
|
failures_for_qf = merged
|
||||||
end
|
end
|
||||||
|
if (not failures_for_qf or #failures_for_qf == 0) and type(fallback_failures) == "table" then
|
||||||
|
local merged = {}
|
||||||
|
local seen = {}
|
||||||
|
for _, name in ipairs(fallback_failures) do
|
||||||
|
if name and not seen[name] then
|
||||||
|
seen[name] = true
|
||||||
|
table.insert(merged, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
failures_for_qf = merged
|
||||||
|
end
|
||||||
if #failures_for_qf > 0 and runner and type(runner.collect_failed_locations) == "function" then
|
if #failures_for_qf > 0 and runner and type(runner.collect_failed_locations) == "function" then
|
||||||
local ok_collect, collected = pcall(runner.collect_failed_locations, failures_for_qf, command, options.scope_kind)
|
local scope_kind = options.qf_scope_kind or options.scope_kind
|
||||||
|
local ok_collect, collected = pcall(runner.collect_failed_locations, failures_for_qf, command, scope_kind)
|
||||||
if ok_collect and type(collected) == "table" then
|
if ok_collect and type(collected) == "table" then
|
||||||
items = collected
|
items = collected
|
||||||
end
|
end
|
||||||
@@ -1903,6 +2391,7 @@ function M.run_last()
|
|||||||
end
|
end
|
||||||
run_command(command, {
|
run_command(command, {
|
||||||
runner = runner,
|
runner = runner,
|
||||||
|
scope_kind = state.last_scope_kind or "last",
|
||||||
output_parser = parser or (runner and runner.parse_results),
|
output_parser = parser or (runner and runner.parse_results),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
@@ -1915,7 +2404,7 @@ function M.run_nearest()
|
|||||||
|
|
||||||
local runner = M.get_runner_for_buf(bufnr)
|
local runner = M.get_runner_for_buf(bufnr)
|
||||||
if not runner then
|
if not runner then
|
||||||
vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN)
|
vim.notify("[test-samurai] no runner installed for this kind of test", vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1959,7 +2448,7 @@ function M.run_file()
|
|||||||
|
|
||||||
local runner = M.get_runner_for_buf(bufnr)
|
local runner = M.get_runner_for_buf(bufnr)
|
||||||
if not runner then
|
if not runner then
|
||||||
vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN)
|
vim.notify("[test-samurai] no runner installed for this kind of test", vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1992,7 +2481,7 @@ function M.run_all()
|
|||||||
|
|
||||||
local runner = M.get_runner_for_buf(bufnr)
|
local runner = M.get_runner_for_buf(bufnr)
|
||||||
if not runner then
|
if not runner then
|
||||||
vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN)
|
vim.notify("[test-samurai] no runner installed for this kind of test", vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2073,10 +2562,17 @@ function M.run_failed_only()
|
|||||||
end
|
end
|
||||||
run_command(command, {
|
run_command(command, {
|
||||||
save_last = false,
|
save_last = false,
|
||||||
|
runner = runner,
|
||||||
output_parser = parser or (runner and runner.parse_results),
|
output_parser = parser or (runner and runner.parse_results),
|
||||||
|
qf_fallback_failures = state.last_scope_failures,
|
||||||
|
qf_scope_kind = state.last_scope_kind,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.close_output_and_restore()
|
||||||
|
close_container_and_restore()
|
||||||
|
end
|
||||||
|
|
||||||
function M.show_output()
|
function M.show_output()
|
||||||
if not (state.last_buf and vim.api.nvim_buf_is_valid(state.last_buf)) then
|
if not (state.last_buf and vim.api.nvim_buf_is_valid(state.last_buf)) then
|
||||||
vim.notify("[test-samurai] No previous output", vim.log.levels.WARN)
|
vim.notify("[test-samurai] No previous output", vim.log.levels.WARN)
|
||||||
|
|||||||
@@ -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
@@ -13,7 +13,7 @@ implementieren muss, damit alle Commands vollständig unterstützt werden.
|
|||||||
- Beispiel: `lua/test-samurai-go-runner/init.lua` -> `require("test-samurai-go-runner")`.
|
- 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.
|
- `lua/init.lua` wäre `require("init")` und ist kollisionsanfällig; vermeiden.
|
||||||
|
|
||||||
## Pflichtfunktionen (volle Command-Unterstützung)
|
## Pflichtfunktionen (volle Command- und Keymap-Unterstützung)
|
||||||
|
|
||||||
- `is_test_file(bufnr) -> boolean`
|
- `is_test_file(bufnr) -> boolean`
|
||||||
- Wird für die Runner-Auswahl genutzt.
|
- Wird für die Runner-Auswahl genutzt.
|
||||||
@@ -29,10 +29,18 @@ implementieren muss, damit alle Commands vollständig unterstützt werden.
|
|||||||
- Für `TSamAll`.
|
- Für `TSamAll`.
|
||||||
- `build_failed_command(last_command, failures, scope_kind) -> command_spec`
|
- `build_failed_command(last_command, failures, scope_kind) -> command_spec`
|
||||||
- Für `TSamFailedOnly`.
|
- 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)
|
## Output-Parsing (Listing + Summary)
|
||||||
|
|
||||||
Genau eine der folgenden Varianten muss vorhanden sein:
|
Beide Varianten müssen vorhanden sein:
|
||||||
|
|
||||||
- `parse_results(output) -> results`
|
- `parse_results(output) -> results`
|
||||||
- `results` muss enthalten:
|
- `results` muss enthalten:
|
||||||
@@ -46,6 +54,9 @@ Genau eine der folgenden Varianten muss vorhanden sein:
|
|||||||
- oder `output_parser() -> { on_line, on_complete }`
|
- oder `output_parser() -> { on_line, on_complete }`
|
||||||
- `on_line(line, state)` kann `results` liefern (siehe oben).
|
- `on_line(line, state)` kann `results` liefern (siehe oben).
|
||||||
- `on_complete(output, 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
|
## Listing-Gruppierung fuer Parent/Subtests
|
||||||
|
|
||||||
@@ -78,6 +89,25 @@ Genau eine der folgenden Varianten muss vorhanden sein:
|
|||||||
- `spec` (von `find_nearest`):
|
- `spec` (von `find_nearest`):
|
||||||
- Muss mindestens `file` enthalten, z. B.:
|
- Muss mindestens `file` enthalten, z. B.:
|
||||||
- `{ file = "...", cwd = "...", test_name = "...", full_name = "...", kind = "..." }`
|
- `{ 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
|
## Optional empfohlene Metadaten
|
||||||
|
|
||||||
@@ -126,6 +156,17 @@ function runner.parse_results(output)
|
|||||||
return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } }
|
return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } }
|
||||||
end
|
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)
|
function runner.parse_test_output(output)
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
@@ -145,21 +186,33 @@ return runner
|
|||||||
- build_file_command implementiert
|
- build_file_command implementiert
|
||||||
- build_all_command implementiert
|
- build_all_command implementiert
|
||||||
- build_failed_command implementiert
|
- build_failed_command implementiert
|
||||||
- parse_results oder output_parser implementiert
|
- parse_results implementiert
|
||||||
|
- output_parser implementiert (Streaming)
|
||||||
- parse_test_output implementiert
|
- parse_test_output implementiert
|
||||||
- collect_failed_locations implementiert
|
- collect_failed_locations implementiert
|
||||||
- command_spec `{ cmd, cwd }` korrekt zurückgegeben
|
- command_spec `{ cmd, cwd }` korrekt zurückgegeben
|
||||||
|
|
||||||
## Projekt- und Prozessanforderungen
|
## Projekt- und Prozessanforderungen
|
||||||
|
|
||||||
- Eine englischsprachige `README.md` ist zu erstellen.
|
- Rolle: **TDD-first Entwickler**.
|
||||||
- Sie muss bei jedem weiteren Prompt aktualisiert werden, wenn die Aenderungen es erfordern.
|
- 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:
|
- TDD-Vorgaben (aus `AGENTS.md`) uebernehmen:
|
||||||
- Neue Funktionen/Commands/Verhaltensaenderungen muessen getestet werden.
|
- Neue Funktionen/Commands/Verhaltensaenderungen muessen getestet werden.
|
||||||
- Tests nach jeder Code-Aenderung ausfuehren.
|
- Tests nach jeder Code-Aenderung ausfuehren.
|
||||||
- Im Runner erstellter Quellcode ist ebenfalls zu testen.
|
- Im Runner erstellter Quellcode ist ebenfalls zu testen.
|
||||||
- Eine eigene `run_test.sh` darf im Runner-Repo angelegt werden.
|
- Eine eigene `run_test.sh` wird im Runner-Repo angelegt.
|
||||||
- Eine Gitea-Action ist zu erstellen, die bei jedem Push die Tests ausfuehrt.
|
- 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):
|
- Beispiel (anpassbarer Workflow):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -176,6 +229,19 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
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
|
- name: Run tests
|
||||||
run: bash run_test.sh
|
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.
|
||||||
|
|||||||
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