summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-01-19 13:52:31 +0100
committerMike Vink <mike@pionative.com>2025-01-19 13:52:31 +0100
commitc65afb488eb9eab85063d79783d40ae1d7138586 (patch)
tree48ce8318f6fc22eb0b82df83b5c175469b853643 /tests
Squashed 'mut/neovim/pack/plugins/start/quicker.nvim/' content from commit 049def7
git-subtree-dir: mut/neovim/pack/plugins/start/quicker.nvim git-subtree-split: 049def718213d3cdf49fdf29835aded09b3e54a3
Diffstat (limited to 'tests')
-rw-r--r--tests/context_spec.lua134
-rw-r--r--tests/display_spec.lua145
-rw-r--r--tests/editor_spec.lua347
-rw-r--r--tests/fs_spec.lua20
-rw-r--r--tests/minimal_init.lua16
-rw-r--r--tests/opts_spec.lua52
-rw-r--r--tests/snapshots/display_15
-rw-r--r--tests/snapshots/display_long_11
-rw-r--r--tests/snapshots/display_minimal_11
-rw-r--r--tests/snapshots/edit_110
-rw-r--r--tests/snapshots/edit_all_whitespace4
-rw-r--r--tests/snapshots/edit_all_whitespace_qf2
-rw-r--r--tests/snapshots/edit_delim10
-rw-r--r--tests/snapshots/edit_dupe10
-rw-r--r--tests/snapshots/edit_dupe_210
-rw-r--r--tests/snapshots/edit_dupe_qf2
-rw-r--r--tests/snapshots/edit_dupe_qf_22
-rw-r--r--tests/snapshots/edit_expanded10
-rw-r--r--tests/snapshots/edit_expanded_qf4
-rw-r--r--tests/snapshots/edit_fail10
-rw-r--r--tests/snapshots/edit_fail_qf1
-rw-r--r--tests/snapshots/edit_invalid1
-rw-r--r--tests/snapshots/edit_ll10
-rw-r--r--tests/snapshots/edit_multiple_110
-rw-r--r--tests/snapshots/edit_multiple_210
-rw-r--r--tests/snapshots/edit_multiple_qf15
-rw-r--r--tests/snapshots/edit_none_whitespace4
-rw-r--r--tests/snapshots/edit_none_whitespace_qf2
-rw-r--r--tests/snapshots/edit_whitespace4
-rw-r--r--tests/snapshots/edit_whitespace_qf2
-rw-r--r--tests/snapshots/expand_13
-rw-r--r--tests/snapshots/expand_216
-rw-r--r--tests/snapshots/expand_319
-rw-r--r--tests/snapshots/expand_dupe_13
-rw-r--r--tests/snapshots/expand_dupe_25
-rw-r--r--tests/snapshots/expand_loclist4
-rw-r--r--tests/snapshots/expand_missing4
-rw-r--r--tests/snapshots/trim_all_whitespace2
-rw-r--r--tests/snapshots/trim_mixed_whitespace2
-rw-r--r--tests/snapshots/trim_whitespace2
-rw-r--r--tests/snapshots/trim_whitespace_expanded5
-rw-r--r--tests/test_util.lua142
-rw-r--r--tests/whitespace_spec.lua83
43 files changed, 1144 insertions, 0 deletions
diff --git a/tests/context_spec.lua b/tests/context_spec.lua
new file mode 100644
index 0000000..4a4ef5a
--- /dev/null
+++ b/tests/context_spec.lua
@@ -0,0 +1,134 @@
+local quicker = require("quicker")
+local test_util = require("tests.test_util")
+
+describe("context", function()
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("expand results", function()
+ local first = test_util.make_tmp_file("expand_1.txt", 10)
+ local second = test_util.make_tmp_file("expand_2.txt", 10)
+ local first_buf = vim.fn.bufadd(first)
+ local second_buf = vim.fn.bufadd(second)
+ vim.fn.setqflist({
+ {
+ bufnr = first_buf,
+ text = "line 2",
+ lnum = 2,
+ valid = 1,
+ },
+ {
+ bufnr = first_buf,
+ text = "line 8",
+ lnum = 8,
+ valid = 1,
+ },
+ {
+ bufnr = second_buf,
+ text = "line 4",
+ lnum = 4,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "expand_1")
+
+ vim.api.nvim_win_set_cursor(0, { 3, 0 })
+ quicker.expand()
+ test_util.assert_snapshot(0, "expand_2")
+ -- Cursor stays on the same item
+ assert.equals(12, vim.api.nvim_win_get_cursor(0)[1])
+ vim.api.nvim_win_set_cursor(0, { 14, 0 })
+
+ -- Expanding again will produce the same result
+ quicker.expand()
+ test_util.assert_snapshot(0, "expand_2")
+ assert.equals(14, vim.api.nvim_win_get_cursor(0)[1])
+
+ -- Expanding again will produce the same result
+ quicker.expand({ add_to_existing = true })
+ test_util.assert_snapshot(0, "expand_3")
+
+ -- Collapsing will return to the original state
+ quicker.collapse()
+ test_util.assert_snapshot(0, "expand_1")
+ assert.equals(3, vim.api.nvim_win_get_cursor(0)[1])
+ end)
+
+ it("expand loclist results", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("expand_loclist.txt", 10))
+ vim.fn.setloclist(0, {
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ valid = 1,
+ },
+ })
+ vim.cmd.lopen()
+ quicker.expand()
+ test_util.assert_snapshot(0, "expand_loclist")
+ end)
+
+ it("expand when items missing bufnr", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("expand_missing.txt", 10))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ valid = 1,
+ },
+ {
+ text = "Valid line with no bufnr",
+ lnum = 4,
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "Invalid line with a bufnr",
+ lnum = 5,
+ valid = 0,
+ },
+ {
+ text = "Invalid line with no bufnr",
+ lnum = 6,
+ valid = 0,
+ },
+ })
+ vim.cmd.copen()
+ quicker.expand()
+ -- The last three lines should be stripped after expansion
+ test_util.assert_snapshot(0, "expand_missing")
+ end)
+
+ it("expand removes duplicate line entries", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("expand_dupe.txt", 10))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 3",
+ lnum = 3,
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 3",
+ lnum = 3,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "expand_dupe_1")
+
+ quicker.expand()
+ test_util.assert_snapshot(0, "expand_dupe_2")
+ end)
+end)
diff --git a/tests/display_spec.lua b/tests/display_spec.lua
new file mode 100644
index 0000000..c3404ad
--- /dev/null
+++ b/tests/display_spec.lua
@@ -0,0 +1,145 @@
+require("plenary.async").tests.add_to_env()
+local config = require("quicker.config")
+local test_util = require("tests.test_util")
+
+local sleep = require("plenary.async.util").sleep
+
+a.describe("display", function()
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("renders quickfix items", function()
+ vim.fn.setqflist({
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ lnum = 5,
+ valid = 1,
+ },
+ {
+ filename = "README.md",
+ text = "text",
+ lnum = 10,
+ col = 0,
+ end_col = 4,
+ nr = 3,
+ type = "E",
+ valid = 1,
+ },
+ {
+ module = "mod",
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ valid = 1,
+ },
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ valid = 0,
+ },
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ lnum = 1,
+ text = "",
+ valid = 0,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "display_1")
+ end)
+
+ a.it("truncates long filenames", function()
+ config.max_filename_width = function()
+ return 10
+ end
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file(string.rep("f", 10) .. ".txt", 10))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 5,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ -- Wait for highlights to be applied
+ sleep(50)
+ test_util.assert_snapshot(0, "display_long_1")
+ end)
+
+ a.it("renders minimal line when no filenames in results", function()
+ vim.fn.setqflist({
+ {
+ text = "text",
+ },
+ })
+ vim.cmd.copen()
+ -- Wait for highlights to be applied
+ sleep(50)
+ test_util.assert_snapshot(0, "display_minimal_1")
+ end)
+
+ a.it("sets signs for diagnostics", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("sign_test.txt", 10))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 1,
+ type = "E",
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 2,
+ type = "W",
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 3,
+ type = "I",
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 4,
+ type = "H",
+ valid = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "text",
+ lnum = 5,
+ type = "N",
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+
+ -- Wait for highlights to be applied
+ sleep(50)
+ local ns = vim.api.nvim_create_namespace("quicker_highlights")
+ local marks = vim.api.nvim_buf_get_extmarks(0, ns, 0, -1, { type = "sign" })
+ assert.equals(5, #marks)
+ local expected = {
+ { "DiagnosticSignError", config.type_icons.E },
+ { "DiagnosticSignWarn", config.type_icons.W },
+ { "DiagnosticSignInfo", config.type_icons.I },
+ { "DiagnosticSignHint", config.type_icons.H },
+ { "DiagnosticSignHint", config.type_icons.N },
+ }
+ for i, mark_data in ipairs(marks) do
+ local extmark_id, row = mark_data[1], mark_data[2]
+ local mark = vim.api.nvim_buf_get_extmark_by_id(0, ns, extmark_id, { details = true })
+ local hl_group, icon = unpack(expected[i])
+ assert.equals(i - 1, row)
+ assert.equals(hl_group, mark[3].sign_hl_group)
+ assert.equals(icon, mark[3].sign_text)
+ end
+ end)
+end)
diff --git a/tests/editor_spec.lua b/tests/editor_spec.lua
new file mode 100644
index 0000000..0999508
--- /dev/null
+++ b/tests/editor_spec.lua
@@ -0,0 +1,347 @@
+local config = require("quicker.config")
+local display = require("quicker.display")
+local quicker = require("quicker")
+local test_util = require("tests.test_util")
+
+---@param lnum integer
+---@param line string
+local function replace_text(lnum, line)
+ local prev_line = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]
+ local idx = prev_line:find(display.EM_QUAD, 1, true)
+ vim.api.nvim_buf_set_text(0, lnum - 1, idx + display.EM_QUAD_LEN - 1, lnum - 1, -1, { line })
+end
+
+---@param lnum integer
+local function del_line(lnum)
+ vim.cmd.normal({ args = { string.format("%dggdd", lnum) }, bang = true })
+end
+
+local function wait_virt_text()
+ vim.wait(10, function()
+ return false
+ end)
+end
+
+describe("editor", function()
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("can edit one line in file", function()
+ vim.cmd.edit({ args = { test_util.make_tmp_file("edit_1.txt", 10) } })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ replace_text(1, "new text")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_1")
+ end)
+
+ it("can edit across multiple files", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("edit_multiple_1.txt", 10))
+ vim.fn.bufload(bufnr)
+ local buf2 = vim.fn.bufadd(test_util.make_tmp_file("edit_multiple_2.txt", 10))
+ vim.fn.bufload(buf2)
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 9",
+ lnum = 9,
+ },
+ {
+ bufnr = buf2,
+ text = "line 5",
+ lnum = 5,
+ },
+ })
+ vim.cmd.copen()
+ quicker.expand()
+ wait_virt_text()
+ replace_text(2, "new text")
+ replace_text(3, "some text")
+ replace_text(7, "other text")
+ replace_text(11, "final text")
+ local last_line = vim.api.nvim_buf_line_count(0)
+ vim.api.nvim_win_set_cursor(0, { last_line, 0 })
+ vim.cmd.write()
+ test_util.assert_snapshot(0, "edit_multiple_qf")
+ test_util.assert_snapshot(bufnr, "edit_multiple_1")
+ test_util.assert_snapshot(buf2, "edit_multiple_2")
+ -- We should keep the cursor position
+ assert.equals(last_line, vim.api.nvim_win_get_cursor(0)[1])
+ end)
+
+ it("can expand then edit expanded line", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("edit_expanded.txt", 10))
+ vim.fn.bufload(bufnr)
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ quicker.expand()
+ wait_virt_text()
+ replace_text(1, "first")
+ replace_text(2, "second")
+ replace_text(3, "third")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_expanded")
+ test_util.assert_snapshot(0, "edit_expanded_qf")
+ end)
+
+ it("fails when source text is different", function()
+ vim.cmd.edit({ args = { test_util.make_tmp_file("edit_fail.txt", 10) } })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "buzz buzz",
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ replace_text(1, "new text")
+ test_util.with(function()
+ local notify = vim.notify
+ ---@diagnostic disable-next-line: duplicate-set-field
+ vim.notify = function() end
+ return function()
+ vim.notify = notify
+ end
+ end, function()
+ vim.cmd.write()
+ end)
+ test_util.assert_snapshot(bufnr, "edit_fail")
+ test_util.assert_snapshot(0, "edit_fail_qf")
+ end)
+
+ it("can handle multiple qf items on same lnum", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("edit_dupe.txt", 10))
+ vim.fn.bufload(bufnr)
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ col = 0,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ col = 3,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ replace_text(1, "first")
+ replace_text(2, "second")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_dupe")
+ test_util.assert_snapshot(0, "edit_dupe_qf")
+
+ -- If only one of them has a change, it should go through
+ replace_text(1, "line 2")
+ replace_text(2, "second")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_dupe_2")
+ test_util.assert_snapshot(0, "edit_dupe_qf_2")
+ end)
+
+ it("handles deleting lines (shrinks quickfix)", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("edit_delete.txt", 10))
+ vim.fn.bufload(bufnr)
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 3",
+ lnum = 3,
+ },
+ {
+ bufnr = bufnr,
+ text = "line 6",
+ lnum = 6,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ del_line(3)
+ del_line(2)
+ vim.cmd.write()
+ assert.are.same({
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ col = 0,
+ end_col = 0,
+ vcol = 0,
+ end_lnum = 0,
+ module = "",
+ nr = 0,
+ pattern = "",
+ type = "",
+ valid = 1,
+ },
+ }, vim.fn.getqflist())
+ end)
+
+ it("handles loclist", function()
+ vim.cmd.edit({ args = { test_util.make_tmp_file("edit_ll.txt", 10) } })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setloclist(0, {
+ {
+ bufnr = bufnr,
+ text = "line 2",
+ lnum = 2,
+ },
+ })
+ vim.cmd.lopen()
+ wait_virt_text()
+ replace_text(1, "new text")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_ll")
+ end)
+
+ it("handles text that contains the delimiter", function()
+ vim.cmd.edit({ args = { test_util.make_tmp_file("edit_delim.txt", 10) } })
+ local bufnr = vim.api.nvim_get_current_buf()
+ local line = "line 2 " .. config.borders.vert .. " text"
+ vim.api.nvim_buf_set_lines(bufnr, 1, 2, false, { line })
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = line,
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ replace_text(1, line .. " " .. config.borders.vert .. " more text")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_delim")
+ end)
+
+ it("can edit lines with trimmed common whitespace", function()
+ require("quicker.config").trim_leading_whitespace = "common"
+ vim.cmd.edit({
+ args = {
+ test_util.make_tmp_file("edit_whitespace.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ " line 4",
+ }),
+ },
+ })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ test_util.assert_snapshot(0, "edit_whitespace_qf")
+ replace_text(1, "foo")
+ replace_text(2, "bar")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_whitespace")
+ end)
+
+ it("can edit lines with trimmed all whitespace", function()
+ require("quicker.config").trim_leading_whitespace = "all"
+ vim.cmd.edit({
+ args = {
+ test_util.make_tmp_file("edit_whitespace.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ " line 4",
+ }),
+ },
+ })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ test_util.assert_snapshot(0, "edit_all_whitespace_qf")
+ replace_text(1, "foo")
+ replace_text(2, "bar")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_all_whitespace")
+ end)
+
+ it("can edit lines with untrimmed whitespace", function()
+ require("quicker.config").trim_leading_whitespace = false
+ vim.cmd.edit({
+ args = {
+ test_util.make_tmp_file("edit_whitespace.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ " line 4",
+ }),
+ },
+ })
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ wait_virt_text()
+ test_util.assert_snapshot(0, "edit_none_whitespace_qf")
+ replace_text(1, "foo")
+ replace_text(2, "bar")
+ vim.cmd.write()
+ test_util.assert_snapshot(bufnr, "edit_none_whitespace")
+ end)
+end)
diff --git a/tests/fs_spec.lua b/tests/fs_spec.lua
new file mode 100644
index 0000000..2e1e54f
--- /dev/null
+++ b/tests/fs_spec.lua
@@ -0,0 +1,20 @@
+local fs = require("quicker.fs")
+
+local home = os.getenv("HOME")
+local cwd = vim.fn.getcwd()
+
+describe("fs", function()
+ it("shortens path", function()
+ assert.equals("~/bar/baz.txt", fs.shorten_path(home .. "/bar/baz.txt"))
+ assert.equals("bar/baz.txt", fs.shorten_path(cwd .. "/bar/baz.txt"))
+ assert.equals("/foo/bar.txt", fs.shorten_path("/foo/bar.txt"))
+ end)
+
+ it("finds subpath", function()
+ assert.truthy(fs.is_subpath("/root", "/root/foo"))
+ assert.truthy(fs.is_subpath(cwd, "foo"))
+ assert.falsy(fs.is_subpath("/root", "/foo"))
+ assert.falsy(fs.is_subpath("/root", "/rooter/foo"))
+ assert.falsy(fs.is_subpath("/root", "/root/../foo"))
+ end)
+end)
diff --git a/tests/minimal_init.lua b/tests/minimal_init.lua
new file mode 100644
index 0000000..486b213
--- /dev/null
+++ b/tests/minimal_init.lua
@@ -0,0 +1,16 @@
+vim.cmd([[set runtimepath+=.]])
+
+vim.o.swapfile = false
+vim.bo.swapfile = false
+require("tests.test_util").reset_editor()
+
+-- TODO test highlighting (both highlight.lua module and adding them in display.lua)
+-- TODO test syntax highlighting when customizing delimiter
+
+vim.api.nvim_create_user_command("RunTests", function(opts)
+ local path = opts.fargs[1] or "tests"
+ require("plenary.test_harness").test_directory(
+ path,
+ { minimal_init = "./tests/minimal_init.lua" }
+ )
+end, { nargs = "?" })
diff --git a/tests/opts_spec.lua b/tests/opts_spec.lua
new file mode 100644
index 0000000..0732da2
--- /dev/null
+++ b/tests/opts_spec.lua
@@ -0,0 +1,52 @@
+local quicker = require("quicker")
+local test_util = require("tests.test_util")
+
+describe("opts", function()
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("sets buffer opts", function()
+ quicker.setup({
+ opts = {
+ buflisted = true,
+ bufhidden = "wipe",
+ cindent = true,
+ },
+ })
+ vim.fn.setqflist({
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ lnum = 5,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ assert.truthy(vim.bo.buflisted)
+ assert.equals("wipe", vim.bo.bufhidden)
+ assert.truthy(vim.bo.cindent)
+ end)
+
+ it("sets window opts", function()
+ quicker.setup({
+ opts = {
+ wrap = false,
+ number = true,
+ list = true,
+ },
+ })
+ vim.fn.setqflist({
+ {
+ bufnr = vim.fn.bufadd("README.md"),
+ text = "text",
+ lnum = 5,
+ valid = 1,
+ },
+ })
+ vim.cmd.copen()
+ assert.falsy(vim.wo.wrap)
+ assert.truthy(vim.wo.number)
+ assert.truthy(vim.wo.list)
+ end)
+end)
diff --git a/tests/snapshots/display_1 b/tests/snapshots/display_1
new file mode 100644
index 0000000..270a164
--- /dev/null
+++ b/tests/snapshots/display_1
@@ -0,0 +1,5 @@
+README.md ┃ 5┃text
+README.md ┃10┃text
+mod  ┃ ┃text
+README.md ┃ ┃text
+README.md ┃ 1┃ \ No newline at end of file
diff --git a/tests/snapshots/display_long_1 b/tests/snapshots/display_long_1
new file mode 100644
index 0000000..d585beb
--- /dev/null
+++ b/tests/snapshots/display_long_1
@@ -0,0 +1 @@
+…ffffffff.txt ┃ 5┃text \ No newline at end of file
diff --git a/tests/snapshots/display_minimal_1 b/tests/snapshots/display_minimal_1
new file mode 100644
index 0000000..46190e7
--- /dev/null
+++ b/tests/snapshots/display_minimal_1
@@ -0,0 +1 @@
+ ┃text \ No newline at end of file
diff --git a/tests/snapshots/edit_1 b/tests/snapshots/edit_1
new file mode 100644
index 0000000..a18ed5a
--- /dev/null
+++ b/tests/snapshots/edit_1
@@ -0,0 +1,10 @@
+line 1
+new text
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/tests/snapshots/edit_all_whitespace b/tests/snapshots/edit_all_whitespace
new file mode 100644
index 0000000..998c877
--- /dev/null
+++ b/tests/snapshots/edit_all_whitespace
@@ -0,0 +1,4 @@
+ line 1
+ foo
+ bar
+ line 4 \ No newline at end of file
diff --git a/tests/snapshots/edit_all_whitespace_qf b/tests/snapshots/edit_all_whitespace_qf
new file mode 100644
index 0000000..baf8533
--- /dev/null
+++ b/tests/snapshots/edit_all_whitespace_qf
@@ -0,0 +1,2 @@
+tests/tmp/edit_whitespace.txt ┃ 2┃line 2
+tests/tmp/edit_whitespace.txt ┃ 3┃line 3 \ No newline at end of file
diff --git a/tests/snapshots/edit_delim b/tests/snapshots/edit_delim
new file mode 100644
index 0000000..75a9e7f
--- /dev/null
+++ b/tests/snapshots/edit_delim
@@ -0,0 +1,10 @@
+line 1
+line 2 ┃ text ┃ more text
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/tests/snapshots/edit_dupe b/tests/snapshots/edit_dupe
new file mode 100644
index 0000000..b3f56ae
--- /dev/null
+++ b/tests/snapshots/edit_dupe
@@ -0,0 +1,10 @@
+line 1
+line 2
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/tests/snapshots/edit_dupe_2 b/tests/snapshots/edit_dupe_2
new file mode 100644
index 0000000..3ae9ccc
--- /dev/null
+++ b/tests/snapshots/edit_dupe_2
@@ -0,0 +1,10 @@
+line 1
+second
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/tests/snapshots/edit_dupe_qf b/tests/snapshots/edit_dupe_qf
new file mode 100644
index 0000000..7e01207
--- /dev/null
+++ b/tests/snapshots/edit_dupe_qf
@@ -0,0 +1,2 @@
+tests/tmp/edit_dupe.txt ┃ 2┃first
+tests/tmp/edit_dupe.txt ┃ 2┃second \ No newline at end of file
diff --git a/tests/snapshots/edit_dupe_qf_2 b/tests/snapshots/edit_dupe_qf_2
new file mode 100644
index 0000000..1acfd8e
--- /dev/null
+++ b/tests/snapshots/edit_dupe_qf_2
@@ -0,0 +1,2 @@
+tests/tmp/edit_dupe.txt ┃ 2┃line 2
+tests/tmp/edit_dupe.txt ┃ 2┃second \ No newline at end of file
diff --git a/tests/snapshots/edit_expanded b/tests/snapshots/edit_expanded
new file mode 100644
index 0000000..afc39ad
--- /dev/null
+++ b/tests/snapshots/edit_expanded
@@ -0,0 +1,10 @@
+first
+second
+third
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/tests/snapshots/edit_expanded_qf b/tests/snapshots/edit_expanded_qf
new file mode 100644
index 0000000..991dd06
--- /dev/null
+++ b/tests/snapshots/edit_expanded_qf
@@ -0,0 +1,4 @@
+  ┃ 1┃first
+tests/tmp/edit_expanded.txt ┃ 2┃second
+  ┃ 3┃third
+  ┃ 4┃line 4 \ No newline at end of file
diff --git a/tests/snapshots/edit_fail b/tests/snapshots/edit_fail
new file mode 100644
index 0000000..b3f56ae
--- /dev/null
+++ b/tests/snapshots/edit_fail
@@ -0,0 +1,10 @@
+line 1
+line 2
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/tests/snapshots/edit_fail_qf b/tests/snapshots/edit_fail_qf
new file mode 100644
index 0000000..3eb9f10
--- /dev/null
+++ b/tests/snapshots/edit_fail_qf
@@ -0,0 +1 @@
+tests/tmp/edit_fail.txt ┃ 2┃new text \ No newline at end of file
diff --git a/tests/snapshots/edit_invalid b/tests/snapshots/edit_invalid
new file mode 100644
index 0000000..386c994
--- /dev/null
+++ b/tests/snapshots/edit_invalid
@@ -0,0 +1 @@
+ ┃ ┃new text
diff --git a/tests/snapshots/edit_ll b/tests/snapshots/edit_ll
new file mode 100644
index 0000000..a18ed5a
--- /dev/null
+++ b/tests/snapshots/edit_ll
@@ -0,0 +1,10 @@
+line 1
+new text
+line 3
+line 4
+line 5
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/tests/snapshots/edit_multiple_1 b/tests/snapshots/edit_multiple_1
new file mode 100644
index 0000000..765403a
--- /dev/null
+++ b/tests/snapshots/edit_multiple_1
@@ -0,0 +1,10 @@
+line 1
+new text
+some text
+line 4
+line 5
+line 6
+line 7
+line 8
+other text
+line 10 \ No newline at end of file
diff --git a/tests/snapshots/edit_multiple_2 b/tests/snapshots/edit_multiple_2
new file mode 100644
index 0000000..c988ea1
--- /dev/null
+++ b/tests/snapshots/edit_multiple_2
@@ -0,0 +1,10 @@
+line 1
+line 2
+line 3
+line 4
+final text
+line 6
+line 7
+line 8
+line 9
+line 10 \ No newline at end of file
diff --git a/tests/snapshots/edit_multiple_qf b/tests/snapshots/edit_multiple_qf
new file mode 100644
index 0000000..3de41ad
--- /dev/null
+++ b/tests/snapshots/edit_multiple_qf
@@ -0,0 +1,15 @@
+  ┃ 1┃line 1
+tests/tmp/edit_multiple_1.txt ┃ 2┃new text
+  ┃ 3┃some text
+  ┃ 4┃line 4
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╂╌╌╂╌╌╌╌╌╌╌╌
+  ┃ 7┃line 7
+  ┃ 8┃line 8
+tests/tmp/edit_multiple_1.txt ┃ 9┃other text
+  ┃10┃line 10
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━╋━━━━━━━━
+  ┃ 3┃line 3
+  ┃ 4┃line 4
+tests/tmp/edit_multiple_2.txt ┃ 5┃final text
+  ┃ 6┃line 6
+  ┃ 7┃line 7 \ No newline at end of file
diff --git a/tests/snapshots/edit_none_whitespace b/tests/snapshots/edit_none_whitespace
new file mode 100644
index 0000000..4b592c2
--- /dev/null
+++ b/tests/snapshots/edit_none_whitespace
@@ -0,0 +1,4 @@
+ line 1
+foo
+bar
+ line 4 \ No newline at end of file
diff --git a/tests/snapshots/edit_none_whitespace_qf b/tests/snapshots/edit_none_whitespace_qf
new file mode 100644
index 0000000..5be47f1
--- /dev/null
+++ b/tests/snapshots/edit_none_whitespace_qf
@@ -0,0 +1,2 @@
+tests/tmp/edit_whitespace.txt ┃ 2┃ line 2
+tests/tmp/edit_whitespace.txt ┃ 3┃ line 3 \ No newline at end of file
diff --git a/tests/snapshots/edit_whitespace b/tests/snapshots/edit_whitespace
new file mode 100644
index 0000000..6a5ca4b
--- /dev/null
+++ b/tests/snapshots/edit_whitespace
@@ -0,0 +1,4 @@
+ line 1
+ foo
+ bar
+ line 4 \ No newline at end of file
diff --git a/tests/snapshots/edit_whitespace_qf b/tests/snapshots/edit_whitespace_qf
new file mode 100644
index 0000000..e26d928
--- /dev/null
+++ b/tests/snapshots/edit_whitespace_qf
@@ -0,0 +1,2 @@
+tests/tmp/edit_whitespace.txt ┃ 2┃line 2
+tests/tmp/edit_whitespace.txt ┃ 3┃ line 3 \ No newline at end of file
diff --git a/tests/snapshots/expand_1 b/tests/snapshots/expand_1
new file mode 100644
index 0000000..ab1901f
--- /dev/null
+++ b/tests/snapshots/expand_1
@@ -0,0 +1,3 @@
+tests/tmp/expand_1.txt ┃ 2┃line 2
+tests/tmp/expand_1.txt ┃ 8┃line 8
+tests/tmp/expand_2.txt ┃ 4┃line 4 \ No newline at end of file
diff --git a/tests/snapshots/expand_2 b/tests/snapshots/expand_2
new file mode 100644
index 0000000..e0a2139
--- /dev/null
+++ b/tests/snapshots/expand_2
@@ -0,0 +1,16 @@
+  ┃ 1┃line 1
+tests/tmp/expand_1.txt ┃ 2┃line 2
+  ┃ 3┃line 3
+  ┃ 4┃line 4
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╂╌╌╂╌╌╌╌╌╌╌╌
+  ┃ 6┃line 6
+  ┃ 7┃line 7
+tests/tmp/expand_1.txt ┃ 8┃line 8
+  ┃ 9┃line 9
+  ┃10┃line 10
+━━━━━━━━━━━━━━━━━━━━━━━╋━━╋━━━━━━━━
+  ┃ 2┃line 2
+  ┃ 3┃line 3
+tests/tmp/expand_2.txt ┃ 4┃line 4
+  ┃ 5┃line 5
+  ┃ 6┃line 6 \ No newline at end of file
diff --git a/tests/snapshots/expand_3 b/tests/snapshots/expand_3
new file mode 100644
index 0000000..0a20a1c
--- /dev/null
+++ b/tests/snapshots/expand_3
@@ -0,0 +1,19 @@
+  ┃ 1┃line 1
+tests/tmp/expand_1.txt ┃ 2┃line 2
+  ┃ 3┃line 3
+  ┃ 4┃line 4
+  ┃ 5┃line 5
+  ┃ 6┃line 6
+  ┃ 7┃line 7
+tests/tmp/expand_1.txt ┃ 8┃line 8
+  ┃ 9┃line 9
+  ┃10┃line 10
+━━━━━━━━━━━━━━━━━━━━━━━╋━━╋━━━━━━━━
+  ┃ 1┃line 1
+  ┃ 2┃line 2
+  ┃ 3┃line 3
+tests/tmp/expand_2.txt ┃ 4┃line 4
+  ┃ 5┃line 5
+  ┃ 6┃line 6
+  ┃ 7┃line 7
+  ┃ 8┃line 8 \ No newline at end of file
diff --git a/tests/snapshots/expand_dupe_1 b/tests/snapshots/expand_dupe_1
new file mode 100644
index 0000000..8e32cb4
--- /dev/null
+++ b/tests/snapshots/expand_dupe_1
@@ -0,0 +1,3 @@
+tests/tmp/expand_dupe.txt ┃ 2┃line 2
+tests/tmp/expand_dupe.txt ┃ 3┃line 3
+tests/tmp/expand_dupe.txt ┃ 3┃line 3 \ No newline at end of file
diff --git a/tests/snapshots/expand_dupe_2 b/tests/snapshots/expand_dupe_2
new file mode 100644
index 0000000..b51efa8
--- /dev/null
+++ b/tests/snapshots/expand_dupe_2
@@ -0,0 +1,5 @@
+  ┃ 1┃line 1
+tests/tmp/expand_dupe.txt ┃ 2┃line 2
+tests/tmp/expand_dupe.txt ┃ 3┃line 3
+  ┃ 4┃line 4
+  ┃ 5┃line 5 \ No newline at end of file
diff --git a/tests/snapshots/expand_loclist b/tests/snapshots/expand_loclist
new file mode 100644
index 0000000..66a6207
--- /dev/null
+++ b/tests/snapshots/expand_loclist
@@ -0,0 +1,4 @@
+  ┃ 1┃line 1
+tests/tmp/expand_loclist.txt ┃ 2┃line 2
+  ┃ 3┃line 3
+  ┃ 4┃line 4 \ No newline at end of file
diff --git a/tests/snapshots/expand_missing b/tests/snapshots/expand_missing
new file mode 100644
index 0000000..f29a273
--- /dev/null
+++ b/tests/snapshots/expand_missing
@@ -0,0 +1,4 @@
+  ┃ 1┃line 1
+tests/tmp/expand_missing.txt ┃ 2┃line 2
+  ┃ 3┃line 3
+  ┃ 4┃line 4 \ No newline at end of file
diff --git a/tests/snapshots/trim_all_whitespace b/tests/snapshots/trim_all_whitespace
new file mode 100644
index 0000000..e664f80
--- /dev/null
+++ b/tests/snapshots/trim_all_whitespace
@@ -0,0 +1,2 @@
+tests/tmp/whitespace_1.txt ┃ 2┃line 2
+tests/tmp/whitespace_1.txt ┃ 3┃line 3 \ No newline at end of file
diff --git a/tests/snapshots/trim_mixed_whitespace b/tests/snapshots/trim_mixed_whitespace
new file mode 100644
index 0000000..f6464d4
--- /dev/null
+++ b/tests/snapshots/trim_mixed_whitespace
@@ -0,0 +1,2 @@
+tests/tmp/mixed_whitespace.txt ┃ 1┃ line 1
+tests/tmp/mixed_whitespace.txt ┃ 2┃ line 2 \ No newline at end of file
diff --git a/tests/snapshots/trim_whitespace b/tests/snapshots/trim_whitespace
new file mode 100644
index 0000000..49a6e20
--- /dev/null
+++ b/tests/snapshots/trim_whitespace
@@ -0,0 +1,2 @@
+tests/tmp/whitespace.txt ┃ 2┃line 2
+tests/tmp/whitespace.txt ┃ 3┃ line 3 \ No newline at end of file
diff --git a/tests/snapshots/trim_whitespace_expanded b/tests/snapshots/trim_whitespace_expanded
new file mode 100644
index 0000000..07b70d1
--- /dev/null
+++ b/tests/snapshots/trim_whitespace_expanded
@@ -0,0 +1,5 @@
+  ┃ 1┃ line 1
+tests/tmp/whitespace.txt ┃ 2┃line 2
+tests/tmp/whitespace.txt ┃ 3┃ line 3
+  ┃ 4┃
+  ┃ 5┃ line 4 \ No newline at end of file
diff --git a/tests/test_util.lua b/tests/test_util.lua
new file mode 100644
index 0000000..94b0a40
--- /dev/null
+++ b/tests/test_util.lua
@@ -0,0 +1,142 @@
+require("plenary.async").tests.add_to_env()
+local M = {}
+
+local tmp_files = {}
+M.reset_editor = function()
+ vim.cmd.tabonly({ mods = { silent = true } })
+ for i, winid in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
+ if i > 1 then
+ vim.api.nvim_win_close(winid, true)
+ end
+ end
+ vim.api.nvim_win_set_buf(0, vim.api.nvim_create_buf(false, true))
+ for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
+ vim.api.nvim_buf_delete(bufnr, { force = true })
+ end
+ vim.fn.setqflist({})
+ vim.fn.setloclist(0, {})
+ for _, filename in ipairs(tmp_files) do
+ vim.uv.fs_unlink(filename)
+ end
+ tmp_files = {}
+
+ require("quicker").setup({
+ header_length = function()
+ -- Make this deterministic so the snapshots are stable
+ return 8
+ end,
+ })
+end
+
+---@param basename string
+---@param lines integer|string[]
+---@return string
+M.make_tmp_file = function(basename, lines)
+ vim.fn.mkdir("tests/tmp", "p")
+ local filename = "tests/tmp/" .. basename
+ table.insert(tmp_files, filename)
+ local f = assert(io.open(filename, "w"))
+ if type(lines) == "table" then
+ for _, line in ipairs(lines) do
+ f:write(line .. "\n")
+ end
+ else
+ for i = 1, lines do
+ f:write("line " .. i .. "\n")
+ end
+ end
+ f:close()
+ return filename
+end
+
+---@param name string
+---@return string[]
+local function load_snapshot(name)
+ local path = "tests/snapshots/" .. name
+ if vim.fn.filereadable(path) == 0 then
+ return {}
+ end
+ local f = assert(io.open(path, "r"))
+ local lines = {}
+ for line in f:lines() do
+ table.insert(lines, line)
+ end
+ f:close()
+ return lines
+end
+
+---@param name string
+---@param lines string[]
+local function save_snapshot(name, lines)
+ vim.fn.mkdir("tests/snapshots", "p")
+ local path = "tests/snapshots/" .. name
+ local f = assert(io.open(path, "w"))
+ f:write(table.concat(lines, "\n"))
+ f:close()
+ return lines
+end
+
+---@param bufnr integer
+---@param name string
+M.assert_snapshot = function(bufnr, name)
+ -- Wait for the virtual text extmarks to be set
+ if vim.bo[bufnr].filetype == "qf" then
+ vim.wait(10, function()
+ return false
+ end)
+ end
+ local util = require("quicker.util")
+ local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
+
+ -- Add virtual text to lines
+ local headers = {}
+ local header_ns = vim.api.nvim_create_namespace("quicker_headers")
+ for i, v in ipairs(lines) do
+ local extmarks = util.get_lnum_extmarks(bufnr, i, v:len())
+ assert(#extmarks <= 1, "Expected at most one extmark per line")
+ local mark = extmarks[1]
+ if mark then
+ local start_col = mark[3]
+ local data = mark[4]
+ local virt_text = table.concat(
+ vim.tbl_map(function(vt)
+ return vt[1]
+ end, data.virt_text),
+ ""
+ )
+ lines[i] = v:sub(0, start_col) .. virt_text .. v:sub(start_col + 1)
+
+ extmarks = util.get_lnum_extmarks(bufnr, i, v:len(), header_ns)
+ assert(#extmarks <= 1, "Expected at most one extmark per line")
+ mark = extmarks[1]
+ if mark and mark[4].virt_lines then
+ table.insert(headers, { i, mark[4].virt_lines[1][1][1] })
+ end
+ end
+ end
+
+ for i = #headers, 1, -1 do
+ local lnum, header = unpack(headers[i])
+ table.insert(lines, lnum, header)
+ end
+
+ if os.getenv("UPDATE_SNAPSHOTS") then
+ save_snapshot(name, lines)
+ else
+ local expected = load_snapshot(name)
+ assert.are.same(expected, lines)
+ end
+end
+
+---@param context fun(): fun()
+---@param fn fun()
+M.with = function(context, fn)
+ local cleanup = context()
+ local ok, err = pcall(fn)
+ cleanup()
+ if not ok then
+ error(err)
+ end
+end
+
+return M
diff --git a/tests/whitespace_spec.lua b/tests/whitespace_spec.lua
new file mode 100644
index 0000000..0933276
--- /dev/null
+++ b/tests/whitespace_spec.lua
@@ -0,0 +1,83 @@
+local quicker = require("quicker")
+local test_util = require("tests.test_util")
+
+describe("whitespace", function()
+ before_each(function()
+ require("quicker.config").trim_leading_whitespace = "common"
+ end)
+ after_each(function()
+ test_util.reset_editor()
+ end)
+
+ it("removes common leading whitespace from valid results", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("whitespace.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ "",
+ " line 4",
+ }))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "trim_whitespace")
+ quicker.expand()
+ test_util.assert_snapshot(0, "trim_whitespace_expanded")
+ end)
+
+ it("handles mixed tabs and spaces", function()
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("mixed_whitespace.txt", {
+ " line 1",
+ "\t\tline 2",
+ }))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 1",
+ lnum = 1,
+ },
+ {
+ bufnr = bufnr,
+ text = "\t\tline 2",
+ lnum = 2,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "trim_mixed_whitespace")
+ end)
+
+ it("removes all leading whitespace", function()
+ require("quicker.config").trim_leading_whitespace = "all"
+ local bufnr = vim.fn.bufadd(test_util.make_tmp_file("whitespace_1.txt", {
+ " line 1",
+ " line 2",
+ " line 3",
+ "",
+ " line 4",
+ }))
+ vim.fn.setqflist({
+ {
+ bufnr = bufnr,
+ text = " line 2",
+ lnum = 2,
+ },
+ {
+ bufnr = bufnr,
+ text = " line 3",
+ lnum = 3,
+ },
+ })
+ vim.cmd.copen()
+ test_util.assert_snapshot(0, "trim_all_whitespace")
+ end)
+end)