Compare commits
14 Commits
6505a91cce
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
456a157549
|
|||
|
8c598002e4
|
|||
|
bd6930adc0
|
|||
|
24bd89a7be
|
|||
|
fd3952bedf
|
|||
|
238d5f9634
|
|||
|
118f84c31e
|
|||
|
924584d8b3
|
|||
|
5d0b4e9dd6
|
|||
|
c538a32307
|
|||
|
e9a7d2029b
|
|||
|
4c2d585f2d
|
|||
|
e315a8e8f2
|
|||
|
1d9b682a58
|
@@ -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**
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -59,12 +59,27 @@ If no runner matches the current test file, test-samurai will show:
|
|||||||
- `TSamLast` -> `<leader>tl`
|
- `TSamLast` -> `<leader>tl`
|
||||||
- `TSamFailedOnly` -> `<leader>te`
|
- `TSamFailedOnly` -> `<leader>te`
|
||||||
- `TSamShowOutput` -> `<leader>to`
|
- `TSamShowOutput` -> `<leader>to`
|
||||||
|
- Help: `:help test-samurai`
|
||||||
|
|
||||||
Additional keymaps:
|
Additional keymaps:
|
||||||
|
|
||||||
- `<leader>qn` -> close the testing floats and jump to the first quickfix entry
|
- Listing navigation:
|
||||||
- `<leader>nf` -> jump to the next `[ FAIL ]` entry in the Test-Listing-Float (wraps to the first)
|
- `<leader>fn` -> [F]ind [N]ext failed test in listing (wraps to the first, opens Detail-Float, works in Detail-Float)
|
||||||
- `<leader>pf` -> jump to the previous `[ FAIL ]` entry in the Test-Listing-Float (wraps to the last)
|
- `<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 UI
|
||||||
|
|
||||||
@@ -74,11 +89,15 @@ Additional keymaps:
|
|||||||
- After `TSamNearest`, `TSamFile`, `TSamAll`, `TSamFailedOnly`, etc., the UI opens in listing mode (only **Test-Listing-Float** visible).
|
- 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).
|
- 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.
|
- 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; `TSamShowOutput` reopens it.
|
- `<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
|
## Runner architecture
|
||||||
|
|
||||||
Runners are standalone Lua modules. All runner modules are expected to implement the full interface so every command and keymap works.
|
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:
|
Required functions:
|
||||||
|
|
||||||
- `is_test_file`
|
- `is_test_file`
|
||||||
@@ -87,6 +106,9 @@ Required functions:
|
|||||||
- `build_file_command`
|
- `build_file_command`
|
||||||
- `build_all_command`
|
- `build_all_command`
|
||||||
- `build_failed_command`
|
- `build_failed_command`
|
||||||
|
- `parse_results`
|
||||||
|
- `output_parser` (must stream listing output via `on_line`)
|
||||||
|
- `parse_test_output`
|
||||||
- `collect_failed_locations`
|
- `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.
|
No runner for your environment exists? No problem: use `runners-agents.md` to guide an AI-assisted runner implementation tailored to your stack.
|
||||||
@@ -100,6 +122,8 @@ No runner for your environment exists? No problem: use `runners-agents.md` to gu
|
|||||||
|
|
||||||
## Development
|
## 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.
|
Tests are written with `plenary.nvim` / `busted`. Mocks and stubs are allowed.
|
||||||
|
|
||||||
Run tests:
|
Run tests:
|
||||||
|
|||||||
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
|
||||||
@@ -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,
|
||||||
@@ -479,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()
|
||||||
@@ -493,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()
|
||||||
@@ -566,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
|
||||||
@@ -600,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()
|
||||||
@@ -614,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()
|
||||||
@@ -626,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
|
||||||
@@ -649,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 }
|
||||||
|
|
||||||
@@ -664,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()
|
||||||
@@ -678,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()
|
||||||
@@ -690,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
|
||||||
@@ -952,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
|
||||||
@@ -985,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()
|
||||||
@@ -999,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
|
||||||
@@ -1126,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()
|
||||||
@@ -1525,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
|
||||||
@@ -1547,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
|
||||||
@@ -1585,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
|
||||||
@@ -1635,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"
|
||||||
@@ -1804,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)
|
||||||
@@ -1823,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 = {}
|
||||||
@@ -1865,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
|
||||||
@@ -1896,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
|
||||||
@@ -2066,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)
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user