Quicktest.nvim
Run your tests inside nvim in split window or popup with live feedback
Install / Use
/learn @quolpr/Quicktest.nvimREADME
Quicktest
- Contextual Test Triggering: Run tests directly from where your cursor is located or execute all tests in the entire file/dir/project.
- Flexible Test Reruns: Rerun tests from any location(with
require('quicktest').run_previous(), keybind is in usage example), automatically opening window or using an existing if it's open. - Live-Scrolling Results: Continuously scroll through test results as they are generated. But stop scrolling if you decided to scroll up.
- Real-Time Feedback: View the results of tests immediately as they run, without waiting for the completion of the test suite.
- Test Duration Timer: Display a timer to monitor the duration of ongoing tests.
- ANSI colors builtin support.
- Easy to write your own adapter: It's just all about running cmd and piping results to
quicktest. - Persistent previous run: After restarting Neovim, Quicktest will remember the previous run and be able to rerun it.
https://github.com/user-attachments/assets/9fcb3e17-f521-4660-9d9a-d9f763de5a1b
Installation
With Lazy:
{
"quolpr/quicktest.nvim",
config = function()
local qt = require("quicktest")
qt.setup({
-- Choose your adapter, here all supported adapters are listed
adapters = {
require("quicktest.adapters.golang")({}),
require("quicktest.adapters.vitest")({}),
require("quicktest.adapters.playwright")({}),
require("quicktest.adapters.pytest")({}),
require("quicktest.adapters.elixir"),
require("quicktest.adapters.criterion"),
require("quicktest.adapters.dart"),
require("quicktest.adapters.rspec"),
},
-- split or popup mode, when argument not specified
default_win_mode = "split",
use_builtin_colorizer = true
})
end,
dependencies = {
"nvim-lua/plenary.nvim",
"MunifTanjim/nui.nvim",
},
keys = {
{
"<leader>tl",
function()
local qt = require("quicktest")
-- current_win_mode return currently opened panel, split or popup
qt.run_line()
-- You can force open split or popup like this:
-- qt.run_line('split')
-- qt.run_line('popup')
end,
desc = "[T]est Run [L]line",
},
{
"<leader>tf",
function()
local qt = require("quicktest")
qt.run_file()
end,
desc = "[T]est Run [F]ile",
},
{
'<leader>td',
function()
local qt = require 'quicktest'
qt.run_dir()
end,
desc = '[T]est Run [D]ir',
},
{
'<leader>ta',
function()
local qt = require 'quicktest'
qt.run_all()
end,
desc = '[T]est Run [A]ll',
},
{
"<leader>tp",
function()
local qt = require("quicktest")
qt.run_previous()
end,
desc = "[T]est Run [P]revious",
},
{
"<leader>tt",
function()
local qt = require("quicktest")
qt.toggle_win("split")
end,
desc = "[T]est [T]oggle Window",
},
{
"<leader>tc",
function()
local qt = require("quicktest")
qt.cancel_current_run()
end,
desc = "[T]est [C]ancel Current Run",
},
},
}
Without Lazy:
local qt = require("quicktest")
-- Choose your adapter, here all supported adapters are listed
qt.setup({
adapters = {
require("quicktest.adapters.golang"),
require("quicktest.adapters.vitest")({}),
require("quicktest.adapters.playwright")({}),
require("quicktest.adapters.pytest")({}),
require("quicktest.adapters.elixir"),
require("quicktest.adapters.criterion"),
require("quicktest.adapters.dart"),
require("quicktest.adapters.rspec"),
},
-- split or popup mode, when argument not specified
default_win_mode = "split",
use_builtin_colorizer = true
})
vim.keymap.set("n", "<leader>tl", qt.run_line, {
desc = "[T]est Run [L]line",
})
vim.keymap.set("n", "<leader>tf", qt.run_file, {
desc = "[T]est Run [F]ile",
})
vim.keymap.set("n", "<leader>td", qt.run_dir, {
desc = "[T]est Run [D]ir",
})
vim.keymap.set("n", "<leader>ta", qt.run_all, {
desc = "[T]est Run [A]ll",
})
vim.keymap.set("n", "<leader>tR", qt.run_previous, {
desc = "[T]est Run [P]revious",
})
-- vim.keymap.set("n", "<leader>tt", function()
-- qt.toggle_win("popup")
-- end, {
-- desc = "[T]est [T]oggle popup window",
-- })
vim.keymap.set("n", "<leader>tt", function()
qt.toggle_win("split")
end, {
desc = "[T]est [T]oggle Window",
})
vim.keymap.set("n", "<leader>tc", function()
qt.cancel_current_run()
end, {
desc = "[T]est [C]ancel Current Run",
})
Commands
:QuicktestRun[Line/File/Dir/All] <win_mode> <adapter> ...<args>
Examples:
:QuicktestRunLine auto auto --my=arg
:QuicktestRunLine popup auto --my=arg
:QuicktestRunLine split auto --my=arg
:QuicktestRunLine split go --my=arg
:QuicktestRunFile split go --my=arg
:QuicktestRunDir split go --my=arg
:QuicktestRunAll split go --my=arg
Api
local qt = require 'quicktest'
-- Choose your adapter, here all supported adapters are listed
qt.setup({
adapters = {
require("quicktest.adapters.golang")({
---@field cwd (fun(bufnr: integer, current: string?): string)?
---@field bin (fun(bufnr: integer, current: string?): string)?
---@field additional_args (fun(bufnr: integer): string[])?
---@field args (fun(bufnr: integer, current: string[]): string[])?
---@field env (fun(bufnr: integer, current: table<string, string>): table<string, string>)?
---@field is_enabled (fun(bufnr: integer, type: RunType, current: boolean): boolean)?
additional_args = function(bufnr) return { '-race', '-count=1' } end
-- bin = function(bufnr, current) return current end
-- cwd = function(bufnr, current) return current end
}),
require("quicktest.adapters.vitest")({
---@class VitestAdapterOptions
---@field cwd (fun(bufnr: integer, current: string?): string)?
---@field bin (fun(bufnr: integer, current: string?): string)?
---@field config_path (fun(bufnr: integer, current: string): string)?
---@field args (fun(bufnr: integer, current: string[]): string[])?
---@field env (fun(bufnr: integer, current: table<string, string>): table<string, string>)?
---@field is_enabled (fun(bufnr: integer, type: RunType, current: boolean): boolean)?
-- bin = function(bufnr, current) return current end
-- cwd = function(bufnr, current) return current end
-- config_path = function(bufnr, current) return current end
}),
require("quicktest.adapters.elixir")({
---@class ElixirAdapterOptions
---@field cwd (fun(bufnr: integer, current: string?): string)?
---@field bin (fun(bufnr: integer, current: string?): string)?
---@field args (fun(bufnr: integer, current: string[]): string[])?
---@field env (fun(bufnr: integer, current: table<string, string>): table<string, string>)?
---@field is_enabled (fun(bufnr: integer, type: RunType, current: boolean): boolean)?
}),
require("quicktest.adapters.playwright")({
---@class PlaywrightAdapterOptions
---@field cwd (fun(bufnr: integer, current: string?): string)?
---@field bin (fun(bufnr: integer, current: string?): string)?
---@field config_path (fun(bufnr: integer, current: string): string)?
---@field args (fun(bufnr: integer, current: string[]): string[])?
---@field env (fun(bufnr: integer, current: table<string, string>): table<string, string>)?
---@field is_enabled (fun(bufnr: integer, type: RunType, current: boolean): boolean)?
}),
require("quicktest.adapters.pytest")({
---@class PytestAdapterOptions
---@field cwd (fun(bufnr: integer, current: string?): string)?
---@field bin (fun(bufnr: integer, current: string?): string)?
---@field args (fun(bufnr: integer, current: string[]): string[])?
---@field env (fun(bufnr: integer, current: table<string, string>): table<string, string>)?
---@field is_enabled (fun(bufnr: integer, type: RunType, current: boolean): boolean)?
-- bin = function(bufnr, current) return current end
-- cwd = function(bufnr, current) return current end
}),
require("quicktest.adapters.elixir")({
---@class ElixirAdapterOptions
---@field cwd (fun(bufnr: integer, current: string?): string)?
---@field bin (fun(bufnr: integer, current: string?): string)?
---@field args (fun(bufnr: integer, current: string[]): string[])?
---@field env (fun(bufnr: integer, current: table<string, string>): table<string, string>)?
---@field is_enabled (fun(bufnr: integer, type: RunType, current: boolean): boolean)?
}),
require("quicktest.adapters.criterion")({
builddir = function(bufnr) return "build" end,
additional_args = function(bufnr) return {'arg1', 'arg2'} end,
}),
require("quicktest.adapters.dart")({
---@class DartAdapterOptions
---@field cwd (fun(bufnr: integer, current: string?): string)?
---@field bin (fun(bufnr: integer, current: string?): string)?
---@field args (fun(bufnr: integer, current: string[]): string[])?
---@field env (fun(bufnr: integer, current: table<string, string>): table<string, string>)?
---@field is_enabled (fun(bufnr: integer, type: RunType, current: boolean): boolean)?
}),
require("quicktest.adapters.rspec")({
---@class RspecAdapterOptions
---@field bin (fun(bufnr: integer, fallback: string): string)?
---@field cwd (fun(bufnr: integer, fallback: string): string)?
---@field is_enabled (fun(bufnr: integer, fallback: boolean): boolean)?
}),
},
-- split or popup mode, when argument not specified
default_win_mode = "split",
})
-- Find nearest test under cursor and run in popup
qt.run_line('popup')
-- Find nearest test under cursor and run in split
qt.run_line('split')
-- Find near
Related Skills
gh-issues
334.5kFetch GitHub issues, spawn sub-agents to implement fixes and open PRs, then monitor and address PR review comments. Usage: /gh-issues [owner/repo] [--label bug] [--limit 5] [--milestone v1.0] [--assignee @me] [--fork user/repo] [--watch] [--interval 5] [--reviews-only] [--cron] [--dry-run] [--model glm-5] [--notify-channel -1002381931352]
node-connect
334.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
xurl
334.5kA CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.
frontend-design
82.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
