1

Update generated neovim config

This commit is contained in:
2024-08-15 14:28:54 +02:00
parent 07409c223d
commit 25cfcf2941
3809 changed files with 351157 additions and 0 deletions

View File

@ -0,0 +1,137 @@
commands = {}
dependencies = {
["nui.nvim"] = {
["0.3.0-1"] = {}
}
}
modules = {
["nui.input.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.layout.float"] = {
"nui.nvim/0.3.0-1"
},
["nui.layout.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.layout.split"] = {
"nui.nvim/0.3.0-1"
},
["nui.layout.utils"] = {
"nui.nvim/0.3.0-1"
},
["nui.line.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.menu.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.object.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.popup.border"] = {
"nui.nvim/0.3.0-1"
},
["nui.popup.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.split.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.split.utils"] = {
"nui.nvim/0.3.0-1"
},
["nui.table.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.text.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.tree.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.tree.util"] = {
"nui.nvim/0.3.0-1"
},
["nui.utils.autocmd"] = {
"nui.nvim/0.3.0-1"
},
["nui.utils.buf_storage"] = {
"nui.nvim/0.3.0-1"
},
["nui.utils.init"] = {
"nui.nvim/0.3.0-1"
},
["nui.utils.keymap"] = {
"nui.nvim/0.3.0-1"
},
["nui/input/README.md"] = {
"nui.nvim/0.3.0-1"
},
["nui/layout/README.md"] = {
"nui.nvim/0.3.0-1"
},
["nui/line/README.md"] = {
"nui.nvim/0.3.0-1"
},
["nui/menu/README.md"] = {
"nui.nvim/0.3.0-1"
},
["nui/popup/README.md"] = {
"nui.nvim/0.3.0-1"
},
["nui/split/README.md"] = {
"nui.nvim/0.3.0-1"
},
["nui/table/README.md"] = {
"nui.nvim/0.3.0-1"
},
["nui/text/README.md"] = {
"nui.nvim/0.3.0-1"
},
["nui/tree/README.md"] = {
"nui.nvim/0.3.0-1"
}
}
repository = {
["nui.nvim"] = {
["0.3.0-1"] = {
{
arch = "installed",
commands = {},
dependencies = {},
modules = {
["nui.input.init"] = "nui/input/init.lua",
["nui.layout.float"] = "nui/layout/float.lua",
["nui.layout.init"] = "nui/layout/init.lua",
["nui.layout.split"] = "nui/layout/split.lua",
["nui.layout.utils"] = "nui/layout/utils.lua",
["nui.line.init"] = "nui/line/init.lua",
["nui.menu.init"] = "nui/menu/init.lua",
["nui.object.init"] = "nui/object/init.lua",
["nui.popup.border"] = "nui/popup/border.lua",
["nui.popup.init"] = "nui/popup/init.lua",
["nui.split.init"] = "nui/split/init.lua",
["nui.split.utils"] = "nui/split/utils.lua",
["nui.table.init"] = "nui/table/init.lua",
["nui.text.init"] = "nui/text/init.lua",
["nui.tree.init"] = "nui/tree/init.lua",
["nui.tree.util"] = "nui/tree/util.lua",
["nui.utils.autocmd"] = "nui/utils/autocmd.lua",
["nui.utils.buf_storage"] = "nui/utils/buf_storage.lua",
["nui.utils.init"] = "nui/utils/init.lua",
["nui.utils.keymap"] = "nui/utils/keymap.lua",
["nui/input/README.md"] = "nui/input/README.md",
["nui/layout/README.md"] = "nui/layout/README.md",
["nui/line/README.md"] = "nui/line/README.md",
["nui/menu/README.md"] = "nui/menu/README.md",
["nui/popup/README.md"] = "nui/popup/README.md",
["nui/split/README.md"] = "nui/split/README.md",
["nui/table/README.md"] = "nui/table/README.md",
["nui/text/README.md"] = "nui/text/README.md",
["nui/tree/README.md"] = "nui/tree/README.md"
}
}
}
}
}

View File

@ -0,0 +1,27 @@
rockspec_format = "3.0"
package = "nui.nvim"
version = "0.3.0-1"
source = {
url = "git+https://github.com/MunifTanjim/nui.nvim.git",
tag = "0.3.0",
}
description = {
summary = "UI Component Library for Neovim.",
detailed = [[
UI Component Library for Neovim.
]],
license = "MIT",
homepage = "https://github.com/MunifTanjim/nui.nvim",
issues_url = "https://github.com/MunifTanjim/nui.nvim/issues",
maintainer = "Munif Tanjim (https://muniftanjim.dev)",
labels = {
"neovim",
},
}
build = {
type = "builtin",
}
test = {
type = "command",
command = "scripts/test.sh",
}

View File

@ -0,0 +1,98 @@
rock_manifest = {
lua = {
nui = {
input = {
["README.md"] = "832b4732e882a065c23f02536578b0cf",
["init.lua"] = "215f8b587a68e5dffa0fdc0278b7a9a6"
},
layout = {
["README.md"] = "cdd351f847db0d9e63ebceb0ada39115",
["float.lua"] = "3d671b671099235a48080e976b8bc74a",
["init.lua"] = "8666f900e2b378fd21a3b29794b2c025",
["split.lua"] = "9853b5bb1c63e58b0008c366674614aa",
["utils.lua"] = "929be3bbfe1837164b2ef864d780b2f6"
},
line = {
["README.md"] = "b8296c09f393e9188acb62d4290c3d1f",
["init.lua"] = "0d315b8ee6460a045069b0a0ca48e672"
},
menu = {
["README.md"] = "fa66ab2637ad3b8cf028946083bfd846",
["init.lua"] = "fe42edae4e918720a8cba636437ca045"
},
object = {
["init.lua"] = "41fd0ddf9ef7f232cbf82e2cd5cb3414"
},
popup = {
["README.md"] = "dbc4ec093f427ebee98ffa0b17045817",
["border.lua"] = "153090d01075ac1b651fc343e916a6ff",
["init.lua"] = "025311d292812d4c0f43d49d4112e40c"
},
split = {
["README.md"] = "eb5e83052df720c5704dc1ce5249c21d",
["init.lua"] = "03c4a20279798d074c35f5b170cb8a4d",
["utils.lua"] = "b67aa70588de13c048e5e324d1357d94"
},
table = {
["README.md"] = "08dfd8dc32600c5b2851487b6153e025",
["init.lua"] = "e27823a8fc0082a54f69b69eb07fabb1"
},
text = {
["README.md"] = "cc3887013d093c19f315565a88a97d66",
["init.lua"] = "9dd10e41e2d2206e568e4d2f0e354bf9"
},
tree = {
["README.md"] = "f493c8a177c3eccf9fe8e38a0c20b51b",
["init.lua"] = "f4603f2e7c9551947755c06da0f70d4b",
["util.lua"] = "a04600b78282ba91e8dfa671977033c0"
},
utils = {
["autocmd.lua"] = "7f0494f2a2ed06dd4247e811306464d6",
["buf_storage.lua"] = "4b2185adfee52329e8174527b69342e6",
["init.lua"] = "0dce69906a083ee8365177bb920610dc",
["keymap.lua"] = "e35ecc6c294836b7b5fde6a6adf32696"
}
}
},
["nui.nvim-0.3.0-1.rockspec"] = "2ef6448d93fc7e0b890c68e71a6b0767",
tests = {
helpers = {
["init.lua"] = "585ca0ddec006fc619c4c77b4725f4cc"
},
["init.lua"] = "157b59cde3d9ce52873d584fbf4357ee",
nui = {
input = {
["init_spec.lua"] = "1f8d79b288391b3452787a11e317e347"
},
layout = {
["init_spec.lua"] = "f82928b2f0ee436340dc92620aed2315",
["utils_spec.lua"] = "595c80e6c8deea405dfe7afc999bdf89"
},
line = {
["init_spec.lua"] = "d3cc98d867d0ebc0a7db151334d0930c"
},
menu = {
["init_spec.lua"] = "3b2d70e12e00e47c3e04206a514e0449"
},
object = {
["init_spec.lua"] = "f499f74b45d8646da1fd25f0a98799f7"
},
popup = {
["border_spec.lua"] = "0496110c6cb5c0d8f616d22c9877b30d",
["init_spec.lua"] = "92549c5c5450df2a8557a2b68cd70269"
},
split = {
["init_spec.lua"] = "03fc8062592a8137860659426eaa7d6d"
},
table = {
["init_spec.lua"] = "223de5324a6ddeb5e3541d6633d8a044"
},
text = {
["init_spec.lua"] = "fc4a92bab680411b2985d2cea9b0ed82"
},
tree = {
["init_spec.lua"] = "1b52b264c8cb7b94cc0420ca26a7cfe1"
}
}
}
}

View File

@ -0,0 +1,312 @@
local function to_string(text)
if type(text) == "string" then
return text
end
if type(text) == "table" then
if text.content then
return text:content()
end
return text[1]
end
error("unsupported text")
end
local popup = {}
local mod = {}
mod.popup = popup
function mod.eq(...)
return assert.are.same(...)
end
function mod.approx(...)
return assert.are.near(...)
end
function mod.neq(...)
return assert["not"].are.same(...)
end
---@param fn fun(): nil
---@param error string
---@param is_plain boolean
function mod.errors(fn, error, is_plain)
assert.matches_error(fn, error, 1, is_plain)
end
---@param keys string
---@param mode string
function mod.feedkeys(keys, mode)
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(keys, true, false, true), mode or "", true)
end
---@param tbl table
---@param keys string[]
function mod.tbl_pick(tbl, keys)
if not keys or #keys == 0 then
return tbl
end
local new_tbl = {}
for _, key in ipairs(keys) do
new_tbl[key] = tbl[key]
end
return new_tbl
end
---@param tbl table
---@param keys string[]
function mod.tbl_omit(tbl, keys)
if not keys or #keys == 0 then
return tbl
end
local new_tbl = vim.deepcopy(tbl)
for _, key in ipairs(keys) do
rawset(new_tbl, key, nil)
end
return new_tbl
end
---@param bufnr number
---@param ns_id integer
---@param linenr integer (1-indexed)
---@param byte_start? integer (0-indexed)
---@param byte_end? integer (0-indexed, inclusive)
function mod.get_line_extmarks(bufnr, ns_id, linenr, byte_start, byte_end)
return vim.api.nvim_buf_get_extmarks(
bufnr,
ns_id,
{ linenr - 1, byte_start or 0 },
{ linenr - 1, byte_end and byte_end + 1 or -1 },
{ details = true }
)
end
---@param bufnr number
---@param ns_id integer
---@param linenr integer (1-indexed)
---@param text string
---@return table[]
---@return { byte_start: integer, byte_end: integer } info (byte range: 0-indexed, inclusive)
function mod.get_text_extmarks(bufnr, ns_id, linenr, text)
local line = vim.api.nvim_buf_get_lines(bufnr, linenr - 1, linenr, false)[1]
local byte_start = string.find(line, text) -- 1-indexed
byte_start = byte_start - 1 -- 0-indexed
local byte_end = byte_start + #text - 1 -- inclusive
local extmarks = vim.api.nvim_buf_get_extmarks(
bufnr,
ns_id,
{ linenr - 1, byte_start },
{ linenr - 1, byte_end },
{ details = true }
)
return extmarks, { byte_start = byte_start, byte_end = byte_end }
end
---@param bufnr number
---@param lines string[]
---@param linenr_start? integer (1-indexed)
---@param linenr_end? integer (1-indexed, inclusive)
function mod.assert_buf_lines(bufnr, lines, linenr_start, linenr_end)
mod.eq(vim.api.nvim_buf_get_lines(bufnr, linenr_start and linenr_start - 1 or 0, linenr_end or -1, false), lines)
end
---@param bufnr number
---@param options table
function mod.assert_buf_options(bufnr, options)
for name, value in pairs(options) do
mod.eq(vim.api.nvim_buf_get_option(bufnr, name), value)
end
end
---@param winid number
---@param options table
function mod.assert_win_options(winid, options)
for name, value in pairs(options) do
mod.eq(vim.api.nvim_win_get_option(winid, name), value)
end
end
---@param extmark table
---@param linenr number (1-indexed)
---@param text string
---@param hl_group string
function mod.assert_extmark(extmark, linenr, text, hl_group)
mod.eq(extmark[2], linenr - 1)
if text then
local start_col = extmark[3]
mod.eq(extmark[4].end_col - start_col, #text)
end
mod.eq(mod.tbl_pick(extmark[4], { "end_row", "hl_group" }), {
end_row = linenr - 1,
hl_group = hl_group,
})
end
---@param bufnr number
---@param ns_id integer
---@param linenr integer (1-indexed)
---@param text string
---@param hl_group string
function mod.assert_highlight(bufnr, ns_id, linenr, text, hl_group)
local extmarks, info = mod.get_text_extmarks(bufnr, ns_id, linenr, text)
mod.eq(#extmarks, 1)
mod.eq(extmarks[1][3], info.byte_start)
mod.assert_extmark(extmarks[1], linenr, text, hl_group)
end
---@param feature_name string
---@param desc string
---@param func fun(is_available: boolean):nil
function mod.describe_flipping_feature(feature_name, desc, func)
local initial_value = require("nui.utils")._.feature[feature_name]
describe(string.format("(w/ %s) %s", feature_name, desc), function()
require("nui.utils")._.feature[feature_name] = true
func(true)
require("nui.utils")._.feature[feature_name] = initial_value
end)
describe(string.format("(w/o %s) %s", feature_name, desc), function()
require("nui.utils")._.feature[feature_name] = false
func(false)
require("nui.utils")._.feature[feature_name] = initial_value
end)
end
function popup.create_border_style_list()
return { "", "", "", "", "", "", "", "" }
end
function popup.create_border_style_map()
return {
top_left = "",
top = "",
top_right = "",
left = "",
right = "",
bottom_left = "",
bottom = "",
bottom_right = "",
}
end
function popup.create_border_style_map_with_tuple(hl_group)
local style = popup.create_border_style_map()
for k, v in pairs(style) do
style[k] = { v, hl_group .. "_" .. k }
end
return style
end
function popup.create_border_style_map_with_nui_text(hl_group)
local Text = require("nui.text")
local style = popup.create_border_style_map()
for k, v in pairs(style) do
style[k] = Text(v, hl_group .. "_" .. k)
end
return style
end
function popup.assert_border_lines(options, border_bufnr)
local size = { width = options.size.width, height = options.size.height }
-- `vim.tbl_islist` will be removed in the future
local islist = vim.islist or vim.tbl_islist
local style = vim.deepcopy(options.border.style)
if islist(style) then
style = {
top_left = style[1],
top = style[2],
top_right = style[3],
left = style[8],
right = style[4],
bottom_left = style[7],
bottom = style[6],
bottom_right = style[5],
}
end
local expected_lines = {}
table.insert(
expected_lines,
string.format(
"%s%s%s",
to_string(style.top_left),
string.rep(to_string(style.top), size.width),
to_string(style.top_right)
)
)
for _ = 1, size.height do
table.insert(
expected_lines,
string.format("%s%s%s", to_string(style.left), string.rep(" ", size.width), to_string(style.right))
)
end
table.insert(
expected_lines,
string.format(
"%s%s%s",
to_string(style.bottom_left),
string.rep(to_string(style.bottom), size.width),
to_string(style.bottom_right)
)
)
mod.assert_buf_lines(border_bufnr, expected_lines)
end
function popup.assert_border_highlight(options, border_bufnr, hl_group, no_hl_group_suffix)
local size = { width = options.size.width, height = options.size.height }
for linenr = 1, size.height + 2 do
local is_top_line = linenr == 1
local is_bottom_line = linenr == size.height + 2
local extmarks = mod.get_line_extmarks(border_bufnr, options.ns_id, linenr)
mod.eq(#extmarks, (is_top_line or is_bottom_line) and 4 or 2)
local function with_suffix(hl_group_name, suffix)
if no_hl_group_suffix then
return hl_group_name
end
return hl_group_name .. suffix
end
mod.assert_extmark(
extmarks[1],
linenr,
nil,
with_suffix(hl_group, (is_top_line and "_top_left" or is_bottom_line and "_bottom_left" or "_left"))
)
if is_top_line or is_bottom_line then
mod.assert_extmark(extmarks[2], linenr, nil, with_suffix(hl_group, (is_top_line and "_top" or "_bottom")))
mod.assert_extmark(extmarks[3], linenr, nil, with_suffix(hl_group, (is_top_line and "_top" or "_bottom")))
end
mod.assert_extmark(
extmarks[#extmarks],
linenr,
nil,
with_suffix(hl_group, (is_top_line and "_top_right" or is_bottom_line and "_bottom_right" or "_right"))
)
end
end
return mod

View File

@ -0,0 +1,24 @@
-- mimic startup option `--clean`
local function clean_startup()
for _, path in ipairs(vim.split(vim.o.runtimepath, ",")) do
if
string.find(path, vim.fn.expand("~/.config/nvim"))
or string.find(path, vim.fn.expand("~/.local/share/nvim/site"))
then
vim.opt.packpath:remove(path)
vim.opt.runtimepath:remove(path)
end
end
end
clean_startup()
local root_dir = vim.fn.fnamemodify(vim.trim(vim.fn.system("git rev-parse --show-toplevel")), ":p"):gsub("/$", "")
package.path = string.format("%s;%s/?.lua;%s/?/init.lua", package.path, root_dir, root_dir)
vim.opt.packpath:prepend(root_dir .. "/.tests/site")
vim.cmd([[
packadd plenary.nvim
]])

View File

@ -0,0 +1,273 @@
pcall(require, "luacov")
local Input = require("nui.input")
local Text = require("nui.text")
local h = require("tests.helpers")
local eq, feedkeys = h.eq, h.feedkeys
-- Input's functionalities are not testable using headless nvim.
-- Not sure what to do about it.
describe("nui.input", function()
local parent_winid, parent_bufnr
local popup_options
local input
before_each(function()
parent_winid = vim.api.nvim_get_current_win()
parent_bufnr = vim.api.nvim_get_current_buf()
popup_options = {
relative = "win",
position = "50%",
size = 20,
}
end)
after_each(function()
if input then
input:unmount()
input = nil
end
end)
pending("o.prompt", function()
it("supports NuiText", function()
local prompt_text = "> "
local hl_group = "NuiInputTest"
input = Input(popup_options, {
prompt = Text(prompt_text, hl_group),
})
input:mount()
h.assert_buf_lines(input.bufnr, {
prompt_text,
})
h.assert_highlight(input.bufnr, input.ns_id, 1, prompt_text, hl_group)
end)
end)
describe("o.on_change", function()
it("works", function()
local done = false
local values = {}
input = Input(popup_options, {
on_change = function(value)
values[#values + 1] = value
end,
on_close = function()
done = true
end,
})
input:mount()
feedkeys("aa", "x") -- append a
feedkeys("ab", "x") -- append b
feedkeys("ac", "x") -- append c
vim.fn.wait(100, function()
return done
end)
eq(values, { "a", "ab", "abc" })
end)
end)
describe("o.on_close", function()
it("is called on <C-c>", function()
local done = false
input = Input(popup_options, {
on_close = function()
done = true
end,
})
input:mount()
feedkeys("i<C-c>", "x")
vim.fn.wait(2000, function()
return done
end)
eq(done, true)
end)
it("is called on unmount", function()
local done = false
input = Input(popup_options, {
on_close = function()
done = true
end,
})
input:mount()
input:unmount()
vim.fn.wait(200, function()
return done
end)
eq(done, true)
end)
end)
describe("cursor_position_patch", function()
local initial_cursor
local function setup()
vim.api.nvim_buf_set_lines(parent_bufnr, 0, -1, false, {
"1 nui.nvim",
"2 nui.nvim",
"3 nui.nvim",
})
initial_cursor = { 2, 4 }
vim.api.nvim_win_set_cursor(parent_winid, initial_cursor)
end
it("works after submitting from insert mode", function()
setup()
local done = false
input = Input(popup_options, {
on_submit = function()
done = true
end,
})
input:mount()
feedkeys("<cr>", "x")
vim.fn.wait(1000, function()
return done
end)
eq(done, true)
eq(vim.api.nvim_win_get_cursor(parent_winid), initial_cursor)
end)
it("works after submitting from normal mode", function()
setup()
local done = false
input = Input(popup_options, {
on_submit = function()
done = true
end,
})
input:mount()
feedkeys("<esc><cr>", "x")
vim.fn.wait(1000, function()
return done
end)
eq(done, true)
eq(vim.api.nvim_win_get_cursor(parent_winid), initial_cursor)
end)
it("works after closing from insert mode", function()
setup()
local done = false
input = Input(popup_options, {
on_close = function()
done = true
end,
})
input:mount()
input:map("i", "<esc>", function()
input:unmount()
end, { nowait = true, noremap = true })
feedkeys("i<esc>", "x")
vim.fn.wait(1000, function()
return done
end)
eq(done, true)
eq(vim.api.nvim_win_get_cursor(parent_winid), initial_cursor)
end)
it("works after closing from normal mode", function()
setup()
local done = false
input = Input(popup_options, {
on_close = function()
done = true
end,
})
input:mount()
input:map("n", "<esc>", function()
input:unmount()
end, { nowait = true, noremap = true })
feedkeys("<esc>", "x")
vim.fn.wait(1000, function()
return done
end)
eq(done, true)
eq(vim.api.nvim_win_get_cursor(parent_winid), initial_cursor)
end)
end)
describe("method :mount", function()
it("is idempotent", function()
input = Input(popup_options, {})
input:mount()
local bufnr, winid = input.bufnr, input.winid
eq(type(bufnr), "number")
eq(type(winid), "number")
input:mount()
eq(bufnr, input.bufnr)
eq(winid, input.winid)
end)
end)
describe("method :unmount", function()
it("is idempotent", function()
local done = 0
input = Input(popup_options, {
on_close = function()
done = done + 1
end,
})
input:mount()
input:unmount()
input:unmount()
input:unmount()
vim.fn.wait(200, function()
return done > 1
end)
eq(done, 1)
end)
end)
end)

View File

@ -0,0 +1,144 @@
pcall(require, "luacov")
local utils = require("nui.layout.utils")
local h = require("tests.helpers")
local eq = h.eq
describe("nui.layout", function()
describe("utils", function()
describe("parse_relative", function()
local fallback_winid = 17
it("works for type=buf", function()
local relative = {
type = "buf",
position = { row = 2, col = 4 },
winid = 42,
}
local result = utils.parse_relative(relative, fallback_winid)
eq(result, {
relative = "win",
win = relative.winid,
bufpos = {
relative.position.row,
relative.position.col,
},
})
end)
it("works for type=cursor", function()
local relative = {
type = "cursor",
winid = 42,
}
local result = utils.parse_relative(relative, fallback_winid)
eq(result, {
relative = relative.type,
win = relative.winid,
})
end)
it("works for type=editor", function()
local relative = {
type = "editor",
winid = 42,
}
local result = utils.parse_relative(relative, fallback_winid)
eq(result, {
relative = relative.type,
win = relative.winid,
})
end)
it("works for type=win", function()
local relative = {
type = "win",
winid = 42,
}
local result = utils.parse_relative(relative, fallback_winid)
eq(result, {
relative = relative.type,
win = relative.winid,
})
end)
it("uses fallback_winid if relative.winid is nil", function()
local relative = {
type = "win",
}
local result = utils.parse_relative(relative, fallback_winid)
eq(result, {
relative = relative.type,
win = fallback_winid,
})
end)
end)
describe("get_container_info", function()
it("works for relative=editor", function()
local result = utils.get_container_info({
relative = "editor",
})
eq(result, {
relative = "editor",
size = {
width = vim.o.columns,
height = vim.o.lines,
},
type = "editor",
})
end)
it("works for relative=cursor", function()
local winid = vim.api.nvim_get_current_win()
local result = utils.get_container_info({
relative = "cursor",
win = 0,
})
eq(result, {
relative = "cursor",
size = {
width = vim.api.nvim_win_get_width(winid),
height = vim.api.nvim_win_get_height(winid),
},
type = "window",
winid = winid,
})
end)
it("works for relative=win w/ bufpos", function()
local winid = vim.api.nvim_get_current_win()
local result = utils.get_container_info({
relative = "win",
win = winid,
bufpos = { 2, 4 },
})
eq(result, {
relative = "buf",
size = {
width = vim.api.nvim_win_get_width(winid),
height = vim.api.nvim_win_get_height(winid),
},
type = "window",
winid = winid,
})
end)
end)
end)
end)

View File

@ -0,0 +1,164 @@
pcall(require, "luacov")
local Line = require("nui.line")
local Text = require("nui.text")
local h = require("tests.helpers")
local eq = h.eq
describe("nui.line", function()
it("can accept initial nui.text objects", function()
local t1, t2 = Text("One"), Text("Two")
local line = Line({ t1, t2 })
eq(#line._texts, 2)
end)
describe("method :append", function()
it("returns nui.text for string parameter", function()
local line = Line()
local text = line:append("One")
eq(type(text.content), "function")
end)
it("returns nui.text for nui.text parameter", function()
local line = Line()
local text = Text("One")
local ret_text = line:append(text)
eq(text == ret_text, true)
eq(type(ret_text.content), "function")
end)
it("returns nui.line for nui.line parameter", function()
local line = Line()
local content_line = Line({ Text("One"), Text("Two") })
local ret_content_line = line:append(content_line)
eq(content_line == ret_content_line, true)
eq(type(ret_content_line.append), "function")
end)
it("stores and returns block with same reference", function()
local line = Line()
local text_one = line:append("One")
eq(line._texts[1] == text_one, true)
local text_two = Text("Two")
local ret_text_two = line:append(text_two)
eq(text_two == ret_text_two, true)
eq(line._texts[2] == text_two, true)
eq(line._texts[2] == ret_text_two, true)
local text_three = Text("Three")
local text_four = Text("Four")
local content_line = Line({ text_three, text_four })
local ret_content_line = line:append(content_line)
eq(content_line == ret_content_line, true)
eq(line._texts[3] == content_line._texts[1], true)
eq(line._texts[4] == content_line._texts[2], true)
end)
end)
describe("method :content", function()
it("returns whole text content", function()
local line = Line()
line:append("One")
line:append("Two")
eq(line:content(), "OneTwo")
end)
end)
describe("method :width", function()
it("returns whole text width", function()
local line = Line()
line:append("One")
line:append("Two")
eq(line:width(), 6)
end)
end)
describe("method", function()
local winid, bufnr
before_each(function()
winid = vim.api.nvim_get_current_win()
bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_win_set_buf(winid, bufnr)
end)
after_each(function()
vim.api.nvim_buf_delete(bufnr, { force = true })
end)
describe(":highlight", function()
local hl_group_one, hl_group_two, ns, ns_id
local linenr
local t1, t2, t3, t4
local line
before_each(function()
hl_group_one = "NuiTextTestOne"
hl_group_two = "NuiTextTestTwo"
ns = "NuiTest"
ns_id = vim.api.nvim_create_namespace(ns)
linenr = 1
t1 = Text("One")
t2 = Text("Two", hl_group_one)
t3 = Text("Three", hl_group_two)
t4 = Text("Four")
line = Line({ t1, t2, t3, t4 })
end)
it("is applied with :render", function()
line:render(bufnr, ns_id, linenr)
h.assert_highlight(bufnr, ns_id, linenr, t2:content(), hl_group_one)
h.assert_highlight(bufnr, ns_id, linenr, t3:content(), hl_group_two)
end)
it("can highlight existing buffer line", function()
vim.api.nvim_buf_set_lines(
bufnr,
linenr - 1,
-1,
false,
{ t1:content() .. t2:content() .. t3:content() .. t4:content() }
)
line:highlight(bufnr, ns_id, linenr)
h.assert_highlight(bufnr, ns_id, linenr, t2:content(), hl_group_one)
h.assert_highlight(bufnr, ns_id, linenr, t3:content(), hl_group_two)
end)
end)
describe(":render", function()
it("works", function()
local linenr = 1
local line = Line()
line:append("4")
line:append("2")
line:render(bufnr, -1, linenr)
h.assert_buf_lines(bufnr, {
"42",
})
end)
end)
end)
end)

View File

@ -0,0 +1,601 @@
pcall(require, "luacov")
local Menu = require("nui.menu")
local Layout = require("nui.layout")
local Line = require("nui.line")
local Text = require("nui.text")
local h = require("tests.helpers")
local spy = require("luassert.spy")
local eq, feedkeys = h.eq, h.feedkeys
describe("nui.menu", function()
local callbacks
local popup_options
local menu
before_each(function()
callbacks = {
on_change = function() end,
on_submit = function() end,
}
popup_options = {
relative = "win",
position = "50%",
}
end)
after_each(function()
if menu then
menu:unmount()
menu = nil
end
end)
describe("method :new", function()
it("works with menu", function()
menu = Menu:new(popup_options, {
lines = {
Menu.item("a"),
},
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
"a",
})
end)
end)
describe("o.keymap", function()
it("supports multiple keys as table", function()
local on_change = spy.on(callbacks, "on_change")
local lines = {
Menu.item("Item 1", { id = 1 }),
Menu.item("Item 2", { id = 2 }),
Menu.item("Item 3", { id = 3 }),
}
menu = Menu(popup_options, {
keymap = {
focus_next = { "j", "s" },
focus_prev = { "k", "w" },
},
lines = lines,
on_change = on_change,
})
menu:mount()
feedkeys("j", "x")
assert.spy(on_change).called_with(lines[2], menu)
on_change:clear()
feedkeys("s", "x")
assert.spy(on_change).called_with(lines[3], menu)
on_change:clear()
feedkeys("w", "x")
assert.spy(on_change).called_with(lines[2], menu)
on_change:clear()
feedkeys("k", "x")
assert.spy(on_change).called_with(lines[1], menu)
on_change:clear()
end)
it("supports single key as string", function()
local on_change = spy.on(callbacks, "on_change")
local lines = {
Menu.item("Item 1", { id = 1 }),
Menu.item("Item 2", { id = 2 }),
Menu.item("Item 3", { id = 3 }),
}
menu = Menu(popup_options, {
keymap = {
focus_next = "s",
focus_prev = "w",
},
lines = lines,
on_change = on_change,
})
menu:mount()
feedkeys("s", "x")
assert.spy(on_change).called_with(lines[2], menu)
on_change:clear()
feedkeys("w", "x")
assert.spy(on_change).called_with(lines[1], menu)
on_change:clear()
end)
end)
describe("size", function()
it("respects o.min_width", function()
local min_width = 3
local items = {
Menu.item("A"),
Menu.separator("*"),
Menu.item("B"),
}
menu = Menu(popup_options, {
lines = items,
min_width = min_width,
})
menu:mount()
eq(vim.api.nvim_win_get_width(menu.winid), min_width)
h.assert_buf_lines(menu.bufnr, {
"A",
" * ",
"B",
})
end)
it("respects o.max_width", function()
local max_width = 6
local items = {
Menu.item("Item 1"),
Menu.separator("*"),
Menu.item("Item Number Two"),
}
menu = Menu(popup_options, {
lines = items,
max_width = max_width,
})
menu:mount()
eq(vim.api.nvim_win_get_width(menu.winid), max_width)
h.assert_buf_lines(menu.bufnr, {
"Item 1",
" * ",
"Item …",
})
end)
it("respects o.min_height", function()
local min_height = 3
local items = {
Menu.item("A"),
Menu.separator("*"),
Menu.item("B"),
}
menu = Menu(popup_options, {
lines = items,
min_height = min_height,
})
menu:mount()
eq(vim.api.nvim_win_get_height(menu.winid), min_height)
end)
it("respects o.max_height", function()
local max_height = 2
local items = {
Menu.item("A"),
Menu.separator("*"),
Menu.item("B"),
}
menu = Menu(popup_options, {
lines = items,
max_height = max_height,
})
menu:mount()
eq(vim.api.nvim_win_get_height(menu.winid), max_height)
end)
end)
it("calls o.on_change item focus is changed", function()
local on_change = spy.on(callbacks, "on_change")
local lines = {
Menu.item("Item 1", { id = 1 }),
Menu.item("Item 2", { id = 2 }),
}
menu = Menu(popup_options, {
lines = lines,
on_change = on_change,
})
menu:mount()
-- initial focus
assert.spy(on_change).called_with(lines[1], menu)
on_change:clear()
feedkeys("j", "x")
assert.spy(on_change).called_with(lines[2], menu)
on_change:clear()
feedkeys("j", "x")
assert.spy(on_change).called_with(lines[1], menu)
on_change:clear()
feedkeys("k", "x")
assert.spy(on_change).called_with(lines[2], menu)
on_change:clear()
end)
it("calls o.on_submit when item is submitted", function()
local on_submit = spy.on(callbacks, "on_submit")
local lines = {
Menu.item("Item 1", { id = 1 }),
Menu.item("Item 2", { id = 2 }),
}
menu = Menu(popup_options, {
lines = lines,
on_submit = on_submit,
})
menu:mount()
feedkeys("j", "x")
feedkeys("<CR>", "x")
assert.spy(on_submit).called_with(lines[2])
end)
it("calls o.on_close when menu is closed", function()
local on_close = spy.on(callbacks, "on_close")
local lines = {
Menu.item("Item 1", { id = 1 }),
Menu.item("Item 2", { id = 2 }),
}
menu = Menu(popup_options, {
lines = lines,
on_close = on_close,
})
menu:mount()
feedkeys("<Esc>", "x")
assert.spy(on_close).called_with()
end)
describe("item", function()
it("is prepared using o.prepare_item if provided", function()
local items = {
Menu.item("A"),
Menu.separator("*"),
Menu.item("B"),
}
local function prepare_item(item)
return "-" .. item.text .. "-"
end
menu = Menu(popup_options, {
lines = items,
prepare_item = prepare_item,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, vim.tbl_map(prepare_item, items))
end)
it("is prepared when o.prepare_item is not provided", function()
local items = {
Menu.item("A"),
Menu.separator("*"),
Menu.item("B"),
}
popup_options.border = "single"
menu = Menu(popup_options, {
lines = items,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
"A",
"─*──",
"B",
})
end)
it("is skipped respecting o.should_skip_item if provided", function()
local on_change = spy.on(callbacks, "on_change")
local items = {
Menu.item("-"),
Menu.item("A", { id = 1 }),
Menu.item("-"),
Menu.item("B", { id = 2 }),
Menu.item("-"),
}
menu = Menu(popup_options, {
lines = items,
on_change = on_change,
should_skip_item = function(item)
return not item.id
end,
})
menu:mount()
assert.spy(on_change).called_with(items[2], menu)
on_change:clear()
feedkeys("j", "x")
assert.spy(on_change).called_with(items[4], menu)
on_change:clear()
feedkeys("j", "x")
assert.spy(on_change).called_with(items[2], menu)
on_change:clear()
end)
it("supports table with key .text", function()
local text = "text"
local items = {
Menu.item({ text = text }),
}
menu = Menu(popup_options, {
lines = items,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
text,
})
end)
it("supports nui.text", function()
local hl_group = "NuiMenuTest"
local text = "text"
local items = {
Menu.item(Text(text, hl_group)),
}
menu = Menu(popup_options, {
lines = items,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
text,
})
h.assert_highlight(menu.bufnr, menu.ns_id, 1, text, hl_group)
end)
it("supports nui.line", function()
local hl_group = "NuiMenuTest"
local text = "text"
local items = {
Menu.item(Line({ Text(text, hl_group) })),
}
menu = Menu(popup_options, {
lines = items,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
text,
})
h.assert_highlight(menu.bufnr, menu.ns_id, 1, text, hl_group)
end)
it("content longer than max_width is truncated", function()
local items = {
Menu.item({ text = "Item 10 -" }),
Menu.item(Text("Item 20 -")),
Menu.item(Line({ Text("Item 30 -") })),
Menu.item(Line({ Text("Item 40"), Text(" -") })),
Menu.item(Line({ Text("Item 50 -"), Text(" -") })),
}
menu = Menu(popup_options, {
max_width = 7,
lines = items,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
"Item 1…",
"Item 2…",
"Item 3…",
"Item 4…",
"Item 5…",
})
end)
end)
describe("separator", function()
it("text supports string", function()
menu = Menu(popup_options, {
lines = {
Menu.item("A"),
Menu.separator("Group"),
},
min_width = 10,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
"A",
" Group ",
})
end)
it("content longer than max_width is truncated", function()
menu = Menu(popup_options, {
lines = {
Menu.item("A"),
Menu.separator("Long Long Group"),
},
max_width = 10,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
"A",
" Long Lo… ",
})
end)
it("text supports nui.text", function()
local hl_group = "NuiMenuTest"
local text = "Group"
menu = Menu(popup_options, {
lines = {
Menu.item("A"),
Menu.separator(Text(text, hl_group)),
},
min_width = 10,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
"A",
" Group ",
})
h.assert_highlight(menu.bufnr, menu.ns_id, 2, text, hl_group)
end)
it("text supports nui.line", function()
local hl_group = "NuiMenuTest"
local text = "Group"
menu = Menu(popup_options, {
lines = {
Menu.item("A"),
Menu.separator(Line({ Text(text, hl_group), Text(" nui.text") })),
},
min_width = 10,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
"A",
" Group nui.t… ",
})
h.assert_highlight(menu.bufnr, menu.ns_id, 2, text, hl_group)
end)
it("o.char supports string", function()
menu = Menu(popup_options, {
lines = {
Menu.item("A"),
Menu.separator("Group", {
char = "*",
text_align = "right",
}),
},
min_width = 10,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
"A",
"****Group*",
})
end)
it("o.char supports nui.text", function()
local hl_group = "NuiMenuTest"
menu = Menu(popup_options, {
lines = {
Menu.item("A"),
Menu.separator("Group", {
char = Text("*", hl_group),
text_align = "center",
}),
},
min_width = 10,
})
menu:mount()
h.assert_buf_lines(menu.bufnr, {
"A",
"**Group***",
})
local linenr = 2
local extmarks = h.get_line_extmarks(menu.bufnr, menu.ns_id, linenr)
eq(#extmarks, 4)
h.assert_extmark(extmarks[1], linenr, "*", hl_group)
h.assert_extmark(extmarks[2], linenr, "*", hl_group)
h.assert_extmark(extmarks[3], linenr, "**", hl_group)
h.assert_extmark(extmarks[4], linenr, "*", hl_group)
end)
end)
describe("w/ Layout", function()
it("can be used", function()
menu = Menu({}, {
lines = {
Menu.item("A"),
},
})
local layout = Layout(
{
position = "50%",
size = "100%",
},
Layout.Box({
Layout.Box(menu, { size = "100%" }),
})
)
layout:mount()
h.assert_buf_lines(menu.bufnr, {
"A",
})
end)
end)
end)

View File

@ -0,0 +1,413 @@
pcall(require, "luacov")
local h = require("tests.helpers")
local Object = require("nui.object")
local spy = require("luassert.spy")
local function assert_class(Class, SuperClass, name)
h.eq(type(Class), "table")
h.eq(Class.super, SuperClass)
h.eq(Class.name, name)
h.eq(tostring(Class), "class " .. name)
h.eq(type(Class.new), "function")
h.eq(type(Class.extend), "function")
local is_callable = pcall(function()
return Class()
end)
h.eq(is_callable, true)
end
local function assert_instance(instance, Class)
h.eq(instance.class, Class)
h.eq(tostring(instance), "instance of class " .. Class.name)
h.eq(instance.name, nil)
h.eq(instance.super, nil)
h.eq(instance.static, nil)
h.eq(instance.new, nil)
h.eq(instance.extend, nil)
end
local function create_classes(...)
local by_name = {}
local classes = {}
for i, def in ipairs({ ... }) do
if type(def) == "string" then
local class = Object(def)
assert_class(class, nil, def)
by_name[def] = class
classes[i] = class
elseif type(def) == "table" then
local super = type(def[2]) == "table" and def[2] or (by_name[def[2]] and by_name[def[2]] or nil)
local class = super and super:extend(def[1]) or Object(def[1])
assert_class(class, super, def[1])
by_name[def[1]] = class
classes[i] = class
else
error("invalid argument")
end
end
return unpack(classes)
end
describe("nui.object", function()
describe("class", function()
it("can be created", function()
local Class = Object("Class")
assert_class(Class, nil, "Class")
end)
describe("static", function()
describe("method", function()
describe(":new", function()
it("is called when creating instance", function()
local Class = Object("Class")
spy.on(Class.static, "new")
Class()
assert.spy(Class.static.new).called_with(Class)
Class.static.new:revert()
spy.on(Class.static, "new")
Class:new()
assert.spy(Class.static.new).called_with(Class)
Class.static.new:revert()
end)
it("creates new instance", function()
local Class = Object("Class")
local instance = Class:new()
assert_instance(instance, Class)
end)
end)
describe(":extend", function()
it("creates subclass", function()
local Class = Object("Class")
local SubClass = Class:extend("SubClass")
assert_class(SubClass, Class, "SubClass")
end)
end)
describe(":is_subclass_of", function()
it("works", function()
local A, B, C = create_classes("A", { "B", "A" }, { "C", "B" })
for _, class in ipairs({ A, B, C }) do
h.eq(class.is_subclass_of, Object.is_subclass)
end
h.eq(A:is_subclass_of(A), false)
h.eq(A:is_subclass_of(B), false)
h.eq(A:is_subclass_of(C), false)
h.eq(B:is_subclass_of(A), true)
h.eq(B:is_subclass_of(B), false)
h.eq(B:is_subclass_of(C), false)
h.eq(C:is_subclass_of(A), true)
h.eq(C:is_subclass_of(B), true)
h.eq(C:is_subclass_of(C), false)
end)
end)
end)
local function define_static_say_level(A)
A.static.level = 1
function A.static.say_level(class)
return "Level: " .. class.level
end
h.eq(A.level, 1)
h.eq(A:say_level(), "Level: 1")
end
it("can be defined for class", function()
local A = create_classes("A")
define_static_say_level(A)
end)
it("is inherited by subclass", function()
local A, B = create_classes("A", { "B", "A" })
define_static_say_level(A)
h.eq(B.level, 1)
h.eq(B:say_level(), "Level: 1")
local C, D = create_classes({ "C", A }, { "D", B })
h.eq(C.level, 1)
h.eq(C:say_level(), "Level: 1")
h.eq(D.level, 1)
h.eq(D:say_level(), "Level: 1")
end)
it("can be redefined for subclass", function()
local A = create_classes("A")
define_static_say_level(A)
local B = create_classes({ "B", A })
B.static.level = 2
h.eq(B:say_level(), "Level: 2")
function B.static.say_level(class)
return "LEVEL: " .. class.level
end
h.eq(B:say_level(), "LEVEL: 2")
local C, D = create_classes({ "C", A }, { "D", B })
C.static.level = 2
h.eq(C:say_level(), "Level: 2")
D.static.level = 3
h.eq(D:say_level(), "LEVEL: 3")
end)
it("for subclass does not affect super", function()
local A = create_classes("A")
define_static_say_level(A)
local B = create_classes({ "B", A })
B.static.level = 2
function B.static.say_level(class)
return "LEVEL: " .. class.level
end
h.eq(A:say_level(), "Level: 1")
local C = create_classes({ "C", B })
function C.static.say_name(class)
return class.name
end
h.eq(C:say_name(), "C")
h.eq(type(C.say_name), "function")
h.eq(type(B.say_name), "nil")
h.eq(type(A.say_name), "nil")
end)
end)
describe("instance", function()
it("can be created", function()
local A = create_classes("A")
local a = A:new()
assert_instance(a, A)
end)
describe("method", function()
describe(":is_instance_of", function()
it("works", function()
local A, B, C, D = create_classes("A", { "B", "A" }, { "C", "B" }, "D")
local a, b, c, d = A:new(), B:new(), C:new(), D:new()
for _, instance in ipairs({ a, b, c, d }) do
h.eq(instance.is_instance_of, Object.is_instance)
end
h.eq(a:is_instance_of(A), true)
h.eq(a:is_instance_of(B), false)
h.eq(a:is_instance_of(C), false)
h.eq(a:is_instance_of(D), false)
h.eq(b:is_instance_of(A), true)
h.eq(b:is_instance_of(B), true)
h.eq(b:is_instance_of(C), false)
h.eq(b:is_instance_of(D), false)
h.eq(c:is_instance_of(A), true)
h.eq(c:is_instance_of(B), true)
h.eq(c:is_instance_of(C), true)
h.eq(c:is_instance_of(D), false)
h.eq(d:is_instance_of(A), false)
h.eq(d:is_instance_of(B), false)
h.eq(d:is_instance_of(C), false)
h.eq(d:is_instance_of(D), true)
end)
end)
it("can be defined", function()
local A = create_classes("A")
function A:before_instance_creation()
return "before " .. self.class.name .. " instance"
end
local a = A:new()
function A:after_instance_creation()
return "after " .. self.class.name .. " instance"
end
h.eq(a:before_instance_creation(), "before A instance")
h.eq(a:after_instance_creation(), "after A instance")
end)
it("can be inherited", function()
local A, B = create_classes("A", { "B", "A" })
function A:say_class_name()
return self.class.name
end
local a = A:new()
h.eq(a:say_class_name(), "A")
local b = B:new()
h.eq(b:say_class_name(), "B")
local C = create_classes({ "C", B })
local c = C:new()
h.eq(c:say_class_name(), "C")
end)
it("can be redefined", function()
local A, B = create_classes("A", { "B", "A" })
function A:say_class_name()
return self.class.name
end
local a = A:new()
h.eq(a:say_class_name(), "A")
function B:say_class_name()
return string.lower(self.class.name)
end
local b = B:new()
h.eq(b:say_class_name(), "b")
local C = create_classes({ "C", B })
local c = C:new()
h.eq(c:say_class_name(), "c")
function C:say_class_name()
return string.rep(self.class.name, 3)
end
h.eq(c:say_class_name(), "CCC")
C.say_class_name = nil
h.eq(c:say_class_name(), "c")
B.say_class_name = nil
h.eq(c:say_class_name(), "C")
end)
end)
describe("metamethod", function()
describe("__index", function()
it("can be set to table", function()
local A = create_classes("A")
function A:upper(str) -- luacheck: no unused args
return string.upper(str)
end
A.__index = {
upper = function(_, str)
return str
end,
lower = function(_, str)
return string.lower(str)
end,
}
local a = A()
h.eq(a:upper("y"), "Y")
h.eq(a:lower("Y"), "y")
A.__index = nil
h.eq(type(a.lower), "nil")
end)
it("can be set to function", function()
local A = create_classes("A")
function A:upper(str) -- luacheck: no unused args
return string.upper(str)
end
local index = {
upper = function(self, str) -- luacheck: no unused args
return str
end,
lower = function(self, str) -- luacheck: no unused args
return string.lower(str)
end,
}
A.__index = function(self, key) -- luacheck: no unused args
return index[key]
end
local a = A()
h.eq(a:upper("y"), "Y")
h.eq(a:lower("Y"), "y")
A.__index = nil
h.eq(type(a.lower), "nil")
end)
end)
describe("__tostring", function()
it("can be redefined", function()
local A, B = create_classes("A", { "B", "A" })
local a = A()
h.eq(tostring(a), "instance of class A")
function A:__tostring()
return "class " .. self.class.name .. "'s child"
end
h.eq(tostring(a), "class A's child")
local b = B()
h.eq(tostring(b), "class B's child")
function B:__tostring()
return "child of " .. self.class.name
end
h.eq(tostring(b), "child of B")
B.__tostring = nil
h.eq(tostring(b), "class B's child")
end)
end)
end)
end)
end)
end)

View File

@ -0,0 +1,632 @@
pcall(require, "luacov")
local Line = require("nui.line")
local Table = require("nui.table")
local Text = require("nui.text")
local h = require("tests.helpers")
local eq = h.eq
describe("nui.table", function()
---@type number, number
local winid, bufnr
before_each(function()
winid = vim.api.nvim_get_current_win()
bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_win_set_buf(winid, bufnr)
end)
after_each(function()
vim.api.nvim_buf_delete(bufnr, { force = true })
end)
describe("o.bufnr", function()
it("throws if missing", function()
local ok, err = pcall(Table, {})
eq(ok, false)
eq(type(string.match(err, "missing bufnr")), "string")
end)
it("throws if invalid", function()
local ok, err = pcall(Table, { bufnr = 999 })
eq(ok, false)
eq(type(string.match(err, "invalid bufnr ")), "string")
end)
it("sets t.bufnr properly", function()
local table = Table({ bufnr = bufnr })
eq(table.bufnr, bufnr)
end)
end)
describe("o.buf_options", function()
it("sets default buf options emulating scratch-buffer", function()
local table = Table({ bufnr = bufnr })
h.assert_buf_options(table.bufnr, {
bufhidden = "hide",
buflisted = false,
buftype = "nofile",
swapfile = false,
})
end)
it("locks buffer by default", function()
local table = Table({ bufnr = bufnr })
h.assert_buf_options(table.bufnr, {
modifiable = false,
readonly = true,
undolevels = 0,
})
end)
it("sets values", function()
local table = Table({
bufnr = bufnr,
buf_options = {
undolevels = -1,
},
})
h.assert_buf_options(table.bufnr, {
undolevels = -1,
})
end)
end)
describe("o.ns_id", function()
it("sets t.ns_id if o.ns_id is string", function()
local ns = "NuiTest"
local table = Table({ bufnr = bufnr, ns_id = ns })
local namespaces = vim.api.nvim_get_namespaces()
eq(table.ns_id, namespaces[ns])
end)
it("sets t.ns_id if o.ns_id is number", function()
local ns = "NuiTest"
local ns_id = vim.api.nvim_create_namespace(ns)
local table = Table({ bufnr = bufnr, ns_id = ns_id })
eq(table.ns_id, ns_id)
end)
end)
describe("o.columns", function()
describe(".id", function()
it("fallbacks t o .accessor_key", function()
local table = Table({
bufnr = bufnr,
columns = { { accessor_key = "ID" } },
data = { { ID = 42 } },
})
table:render()
vim.api.nvim_win_set_cursor(winid, { 2, 3 })
eq(table:get_cell().column.id, "ID")
end)
for header_type, header in pairs({
string = "ID",
NuiText = Text("ID"),
NuiLine = Line({ Text("I"), Text("D") }),
}) do
it(string.format("fallbacks to .header (%s)", header_type), function()
local table = Table({
bufnr = bufnr,
columns = {
{
header = header,
accessor_fn = function()
return ""
end,
},
},
data = { {} },
})
table:render()
vim.api.nvim_win_set_cursor(winid, { 4, 3 })
eq(table:get_cell().column.id, "ID")
end)
end
it("throws if missing", function()
local ok, err = pcall(function()
return Table({
bufnr = bufnr,
columns = { {} },
})
end)
eq(ok, false)
eq(type(string.match(err, "missing column id")), "string")
end)
end)
end)
describe("method :render", function()
local columns
local data
before_each(function()
columns = {
{
header = "First Name",
accessor_key = "firstName",
footer = "firstName",
},
{
header = "Last Name",
accessor_key = "lastName",
footer = "lastName",
},
}
data = {
{
firstName = "tanner",
lastName = "linsley",
age = 24,
visits = 100,
status = "In Relationship",
progress = 50,
},
{
firstName = "tandy",
lastName = "miller",
age = 40,
visits = 40,
status = "Single",
progress = 80,
},
{
firstName = "joe",
lastName = "dirte",
age = 45,
visits = 20,
status = "Complicated",
progress = 10,
},
}
end)
it("can handle empty columns", function()
local table = Table({
bufnr = bufnr,
data = data,
})
table:render()
h.assert_buf_lines(table.bufnr, { "" })
end)
it("can handle empty data", function()
local table = Table({
bufnr = bufnr,
columns = {
{
accessor_key = "firstName",
},
},
})
table:render()
h.assert_buf_lines(table.bufnr, { "" })
end)
it("can handle empty columns and data", function()
local table = Table({ bufnr = bufnr })
table:render()
h.assert_buf_lines(table.bufnr, { "" })
end)
it("works w/ header w/ footer", function()
local table = Table({
bufnr = bufnr,
columns = columns,
data = data,
})
table:render()
h.assert_buf_lines(table.bufnr, {
"┌──────────┬─────────┐",
"│First Name│Last Name│",
"├──────────┼─────────┤",
"│tanner │linsley │",
"├──────────┼─────────┤",
"│tandy │miller │",
"├──────────┼─────────┤",
"│joe │dirte │",
"├──────────┼─────────┤",
"│firstName │lastName │",
"└──────────┴─────────┘",
})
end)
it("works w/ header w/o footer", function()
for _, column in ipairs(columns) do
column.align = "center"
column.footer = nil
end
local table = Table({
bufnr = bufnr,
columns = columns,
data = data,
})
table:render()
h.assert_buf_lines(table.bufnr, {
"┌──────────┬─────────┐",
"│First Name│Last Name│",
"├──────────┼─────────┤",
"│ tanner │ linsley │",
"├──────────┼─────────┤",
"│ tandy │ miller │",
"├──────────┼─────────┤",
"│ joe │ dirte │",
"└──────────┴─────────┘",
})
end)
it("works w/o header w/ footer", function()
for _, column in ipairs(columns) do
column.header = nil
end
local table = Table({
bufnr = bufnr,
columns = columns,
data = data,
})
table:render()
h.assert_buf_lines(table.bufnr, {
"┌─────────┬────────┐",
"│tanner │linsley │",
"├─────────┼────────┤",
"│tandy │miller │",
"├─────────┼────────┤",
"│joe │dirte │",
"├─────────┼────────┤",
"│firstName│lastName│",
"└─────────┴────────┘",
})
end)
it("works w/o header w/o footer", function()
for _, column in ipairs(columns) do
column.header = nil
column.footer = nil
end
local table = Table({
bufnr = bufnr,
columns = columns,
data = data,
})
table:render()
h.assert_buf_lines(table.bufnr, {
"┌──────┬───────┐",
"│tanner│linsley│",
"├──────┼───────┤",
"│tandy │miller │",
"├──────┼───────┤",
"│joe │dirte │",
"└──────┴───────┘",
})
end)
it("supports param linenr_start", function()
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
"START: NuiTest",
"",
"END: NuiTest",
})
local table = Table({
bufnr = bufnr,
columns = columns,
data = { data[1] },
})
table:render(2)
h.assert_buf_lines(table.bufnr, {
"START: NuiTest",
"┌──────────┬─────────┐",
"│First Name│Last Name│",
"├──────────┼─────────┤",
"│tanner │linsley │",
"├──────────┼─────────┤",
"│firstName │lastName │",
"└──────────┴─────────┘",
"END: NuiTest",
})
table:render(4)
h.assert_buf_lines(table.bufnr, {
"START: NuiTest",
"",
"",
"┌──────────┬─────────┐",
"│First Name│Last Name│",
"├──────────┼─────────┤",
"│tanner │linsley │",
"├──────────┼─────────┤",
"│firstName │lastName │",
"└──────────┴─────────┘",
"END: NuiTest",
})
table:render(3)
h.assert_buf_lines(table.bufnr, {
"START: NuiTest",
"",
"┌──────────┬─────────┐",
"│First Name│Last Name│",
"├──────────┼─────────┤",
"│tanner │linsley │",
"├──────────┼─────────┤",
"│firstName │lastName │",
"└──────────┴─────────┘",
"END: NuiTest",
})
end)
describe("grouped columns", function()
local grouped_columns
before_each(function()
grouped_columns = {
{
header = "Name",
footer = function(info)
return info.column.id
end,
columns = {
{
accessor_key = "firstName",
footer = "firstName",
},
{
id = "lastName",
header = "Last Name",
accessor_key = "lastName",
footer = function(info)
return info.column.id
end,
},
},
},
{
header = "Info",
footer = function(info)
return info.column.id
end,
columns = {
{
header = "Age",
accessor_key = "age",
footer = "age",
},
{
header = "More Info",
footer = function(info)
return info.column.id
end,
columns = {
{
accessor_key = "visits",
header = "Visits",
footer = function(info)
return info.column.id
end,
},
{
accessor_key = "status",
header = "Status",
footer = function(info)
return info.column.id
end,
},
},
},
},
},
{
header = "Profile Progress",
accessor_key = "progress",
footer = function(info)
return info.column.id
end,
},
}
end)
it("is drawn correctly", function()
local table = Table({
bufnr = bufnr,
columns = grouped_columns,
data = data,
})
table:render()
h.assert_buf_lines(table.bufnr, {
"┌───────────────────┬──────────────────────────┬────────────────┐",
"│Name │Info │ │",
"├─────────┬─────────┼───┬──────────────────────┤ │",
"│ │ │ │More Info │ │",
"│ │ │ ├──────┬───────────────┤ │",
"│firstName│Last Name│Age│Visits│Status │Profile Progress│",
"├─────────┼─────────┼───┼──────┼───────────────┼────────────────┤",
"│tanner │linsley │24 │100 │In Relationship│50 │",
"├─────────┼─────────┼───┼──────┼───────────────┼────────────────┤",
"│tandy │miller │40 │40 │Single │80 │",
"├─────────┼─────────┼───┼──────┼───────────────┼────────────────┤",
"│joe │dirte │45 │20 │Complicated │10 │",
"├─────────┼─────────┼───┼──────┼───────────────┼────────────────┤",
"│firstName│lastName │age│visits│status │progress │",
"│ │ │ ├──────┴───────────────┤ │",
"│ │ │ │More Info │ │",
"├─────────┴─────────┼───┴──────────────────────┤ │",
"│Name │Info │ │",
"└───────────────────┴──────────────────────────┴────────────────┘",
})
end)
end)
end)
describe("method :get_cell", function()
it("returns nil on border", function()
local table = Table({
bufnr = bufnr,
columns = { { accessor_key = "value" } },
data = { { value = "Such Value!" } },
})
table:render()
vim.api.nvim_win_set_cursor(winid, { 1, 5 })
local cell = table:get_cell()
eq(cell, nil)
end)
it("works after shifting", function()
local table = Table({
bufnr = bufnr,
columns = { { accessor_key = "value" } },
data = { { id = 0, value = "Such Value!" } },
})
table:render()
local cell
vim.api.nvim_win_set_cursor(winid, { 2, 5 })
cell = table:get_cell()
eq(type(cell), "table")
eq(cell.row.original.id, 0)
table:render(2)
vim.api.nvim_win_set_cursor(winid, { 2, 5 })
cell = table:get_cell()
eq(type(cell), "nil")
vim.api.nvim_win_set_cursor(winid, { 3, 5 })
cell = table:get_cell()
eq(type(cell), "table")
eq(cell.row.original.id, 0)
end)
it("can take position", function()
local table = Table({
bufnr = bufnr,
columns = {
{ accessor_key = "id" },
{ accessor_key = "value" },
},
data = {
{ id = 1, value = "One" },
{ id = 2, value = "Two" },
},
})
table:render()
local cell
vim.api.nvim_win_set_cursor(winid, { 2, 3 })
cell = table:get_cell()
eq(cell.get_value(), 1)
cell = table:get_cell({ 1, 1 })
eq(cell.get_value(), "Two")
end)
end)
describe("method :refresh_cell", function()
it("can truncate NuiText on refesh", function()
local table = Table({
bufnr = bufnr,
columns = { { accessor_key = "value" } },
data = { { value = "Such Value!" } },
})
table:render()
h.assert_buf_lines(table.bufnr, {
"┌───────────┐",
"│Such Value!│",
"└───────────┘",
})
vim.api.nvim_win_set_cursor(winid, { 2, 5 })
local cell = table:get_cell()
cell.row.original.value = "Such Looooooog Value!"
table:refresh_cell(cell)
h.assert_buf_lines(table.bufnr, {
"┌───────────┐",
"│Such Loooo…│",
"└───────────┘",
})
end)
it("can truncate NuiLine on refesh", function()
local table = Table({
bufnr = bufnr,
columns = {
{
accessor_key = "value",
cell = function(cell)
return Line({ Text(tostring(cell.get_value()), "NuiTest"), Text(" years old") })
end,
},
},
data = { { value = 42 } },
})
table:render()
h.assert_buf_lines(table.bufnr, {
"┌────────────┐",
"│42 years old│",
"└────────────┘",
})
vim.api.nvim_win_set_cursor(winid, { 2, 5 })
local cell = table:get_cell()
eq(type(cell), "table")
cell.row.original.value = 100
table:refresh_cell(cell)
h.assert_buf_lines(table.bufnr, {
"┌────────────┐",
"│100 years o…│",
"└────────────┘",
})
end)
end)
end)

View File

@ -0,0 +1,284 @@
pcall(require, "luacov")
local Text = require("nui.text")
local h = require("tests.helpers")
local spy = require("luassert.spy")
local eq, tbl_omit = h.eq, h.tbl_omit
describe("nui.text", function()
local multibyte_char
before_each(function()
multibyte_char = ""
end)
it("can clone nui.text object", function()
local hl_group = "NuiTextTest"
local t1 = Text("42", hl_group)
t1.extmark.id = 42
local t2 = Text(t1)
eq(t2:content(), t1:content())
eq(t2.extmark, tbl_omit(t1.extmark, { "id" }))
t2.extmark.id = 42
local t3 = Text(t2)
eq(t3:content(), t2:content())
eq(t3.extmark, tbl_omit(t2.extmark, { "id" }))
end)
it("can clone nui.text object overriding extmark", function()
local hl_group = "NuiTextTest"
local hl_group_override = "NuiTextTestOverride"
local t1 = Text("42", hl_group)
t1.extmark.id = 42
local t2 = Text(t1, hl_group_override)
eq(t2:content(), t1:content())
eq(t2.extmark, { hl_group = hl_group_override })
local t3 = Text(t2, { id = 42, hl_group = hl_group })
eq(t3:content(), t2:content())
eq(t3.extmark, { hl_group = hl_group })
end)
describe("method :set", function()
it("works", function()
local hl_group = "NuiTextTest"
local hl_group_override = "NuiTextTestOverride"
local text = Text("42", hl_group)
eq(text:content(), "42")
eq(text:length(), 2)
eq(text.extmark, {
hl_group = hl_group,
})
text.extmark.id = 42
text:set("3")
eq(text:content(), "3")
eq(text:length(), 1)
eq(text.extmark, {
hl_group = hl_group,
id = 42,
})
text:set("9", hl_group_override)
eq(text:content(), "9")
eq(text.extmark, {
hl_group = hl_group_override,
id = 42,
})
text:set("11", { hl_group = hl_group })
eq(text:content(), "11")
eq(text.extmark, {
hl_group = hl_group,
id = 42,
})
text.extmark.id = nil
text:set("42", { id = 42, hl_group = hl_group })
eq(text:content(), "42")
eq(text.extmark, { hl_group = hl_group })
end)
end)
describe("method :content", function()
it("works", function()
local content = "42"
local text = Text(content)
eq(text:content(), content)
local multibyte_content = multibyte_char
local multibyte_text = Text(multibyte_content)
eq(multibyte_text:content(), multibyte_content)
end)
end)
describe("method :length", function()
it("works", function()
local content = "42"
local text = Text(content)
eq(text:length(), 2)
eq(text:length(), vim.fn.strlen(content))
local multibyte_content = multibyte_char
local multibyte_text = Text(multibyte_content)
eq(multibyte_text:length(), 3)
eq(multibyte_text:length(), vim.fn.strlen(multibyte_content))
end)
end)
describe("method :width", function()
it("works", function()
local content = "42"
local text = Text(content)
eq(text:width(), 2)
eq(text:width(), vim.fn.strwidth(content))
local multibyte_content = multibyte_char
local multibyte_text = Text(multibyte_content)
eq(multibyte_text:width(), 1)
eq(multibyte_text:width(), vim.fn.strwidth(multibyte_content))
end)
end)
describe("method", function()
local winid, bufnr
local initial_lines
before_each(function()
winid = vim.api.nvim_get_current_win()
bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_win_set_buf(winid, bufnr)
initial_lines = { " 1", multibyte_char .. " 2", " 3" }
end)
after_each(function()
vim.api.nvim_buf_delete(bufnr, { force = true })
end)
local function reset_lines(lines)
initial_lines = lines or initial_lines
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, initial_lines)
end
describe(":highlight", function()
local hl_group, ns, ns_id
local linenr, byte_start
local text
before_each(function()
hl_group = "NuiTextTest"
ns = "NuiTest"
ns_id = vim.api.nvim_create_namespace(ns)
end)
it("is applied with :render", function()
reset_lines()
linenr, byte_start = 1, 0
text = Text("a", hl_group)
text:render(bufnr, ns_id, linenr, byte_start)
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
end)
it("is applied with :render_char", function()
reset_lines()
linenr, byte_start = 1, 0
text = Text(multibyte_char, hl_group)
text:render_char(bufnr, ns_id, linenr, byte_start)
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
end)
it("can highlight existing buffer text", function()
reset_lines()
linenr, byte_start = 2, 0
text = Text(initial_lines[linenr], hl_group)
text:highlight(bufnr, ns_id, linenr, byte_start)
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
end)
it("does not create multiple extmarks", function()
reset_lines()
linenr, byte_start = 2, 0
text = Text(initial_lines[linenr], hl_group)
text:highlight(bufnr, ns_id, linenr, byte_start)
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
text:highlight(bufnr, ns_id, linenr, byte_start)
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
text:highlight(bufnr, ns_id, linenr, byte_start)
h.assert_highlight(bufnr, ns_id, linenr, text:content(), hl_group)
end)
end)
describe(":render", function()
it("works on line with singlebyte characters", function()
reset_lines()
local text = Text("a")
spy.on(text, "highlight")
text:render(bufnr, -1, 1, 1)
assert.spy(text.highlight).was_called(1)
assert.spy(text.highlight).was_called_with(text, bufnr, -1, 1, 1)
h.assert_buf_lines(bufnr, {
" a1",
initial_lines[2],
initial_lines[3],
})
end)
it("works on line with multibyte characters", function()
reset_lines()
local text = Text("a")
spy.on(text, "highlight")
text:render(bufnr, -1, 2, vim.fn.strlen(multibyte_char))
assert.spy(text.highlight).was_called(1)
assert.spy(text.highlight).was_called_with(text, bufnr, -1, 2, vim.fn.strlen(multibyte_char))
h.assert_buf_lines(bufnr, {
initial_lines[1],
multibyte_char .. "a2",
initial_lines[3],
})
end)
end)
describe(":render_char", function()
it("works on line with singlebyte characters", function()
reset_lines()
local text = Text("a")
spy.on(text, "highlight")
text:render_char(bufnr, -1, 1, 1)
assert.spy(text.highlight).was_called(1)
assert.spy(text.highlight).was_called_with(text, bufnr, -1, 1, 1)
h.assert_buf_lines(bufnr, {
" a1",
initial_lines[2],
initial_lines[3],
})
end)
it("works on line with multibyte characters", function()
reset_lines()
local text = Text("a")
spy.on(text, "highlight")
text:render_char(bufnr, -1, 2, 1)
assert.spy(text.highlight).was_called(1)
assert.spy(text.highlight).was_called_with(text, bufnr, -1, 2, vim.fn.strlen(multibyte_char))
h.assert_buf_lines(bufnr, {
initial_lines[1],
multibyte_char .. "a2",
initial_lines[3],
})
end)
end)
end)
end)

View File

@ -0,0 +1,912 @@
pcall(require, "luacov")
local Text = require("nui.text")
local Tree = require("nui.tree")
local h = require("tests.helpers")
local eq = h.eq
describe("nui.tree", function()
local winid, bufnr
before_each(function()
winid = vim.api.nvim_get_current_win()
bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_win_set_buf(winid, bufnr)
end)
after_each(function()
vim.api.nvim_buf_delete(bufnr, { force = true })
end)
describe("(#deprecated) o.winid", function()
it("throws if missing", function()
local ok, err = pcall(function()
return Tree({})
end)
eq(ok, false)
eq(type(string.match(err, "missing bufnr")), "string")
end)
it("throws if invalid", function()
local ok, err = pcall(function()
return Tree({ winid = 999 })
end)
eq(ok, false)
eq(type(string.match(err, "invalid winid ")), "string")
end)
it("sets t.winid and t.bufnr properly", function()
local tree = Tree({ winid = winid })
eq(tree.winid, winid)
eq(tree.bufnr, bufnr)
end)
end)
describe("o.bufnr", function()
it("throws if missing", function()
local ok, err = pcall(function()
return Tree({})
end)
eq(ok, false)
eq(type(string.match(err, "missing bufnr")), "string")
end)
it("throws if invalid", function()
local ok, err = pcall(function()
return Tree({ bufnr = 999 })
end)
eq(ok, false)
eq(type(string.match(err, "invalid bufnr ")), "string")
end)
it("sets t.bufnr properly", function()
local tree = Tree({ bufnr = bufnr })
eq(tree.winid, nil)
eq(tree.bufnr, bufnr)
end)
end)
it("throws on duplicated node id", function()
local ok, err = pcall(function()
return Tree({
bufnr = bufnr,
nodes = {
Tree.Node({ id = "id", text = "text" }),
Tree.Node({ id = "id", text = "text" }),
},
})
end)
eq(ok, false)
eq(type(err), "string")
end)
it("sets default buf options emulating scratch-buffer", function()
local tree = Tree({ bufnr = bufnr })
h.assert_buf_options(tree.bufnr, {
bufhidden = "hide",
buflisted = false,
buftype = "nofile",
swapfile = false,
})
end)
describe("(#deprecated) o.win_options", function()
it("sets default values for handling folds", function()
local tree = Tree({ winid = winid })
h.assert_win_options(tree.winid, {
foldmethod = "manual",
foldcolumn = "0",
wrap = false,
})
end)
it("sets values", function()
local initial_statusline = vim.api.nvim_win_get_option(winid, "statusline")
local statusline = "test: win_options " .. math.random()
local tree = Tree({
winid = winid,
win_options = {
statusline = statusline,
},
})
h.assert_win_options(tree.winid, {
statusline = statusline,
})
vim.api.nvim_win_set_option(tree.winid, "statusline", initial_statusline)
end)
it("has no effect if o.bufnr is present", function()
local initial_statusline = vim.api.nvim_win_get_option(winid, "statusline")
Tree({
bufnr = bufnr,
win_options = {
statusline = "test: win_options" .. math.random(),
},
})
h.assert_win_options(winid, {
statusline = initial_statusline,
})
end)
end)
it("sets t.ns_id if o.ns_id is string", function()
local ns = "NuiTreeTest"
local tree = Tree({ bufnr = bufnr, ns_id = ns })
local namespaces = vim.api.nvim_get_namespaces()
eq(tree.ns_id, namespaces[ns])
end)
it("sets t.ns_id if o.ns_id is number", function()
local ns = "NuiTreeTest"
local ns_id = vim.api.nvim_create_namespace(ns)
local tree = Tree({ bufnr = bufnr, ns_id = ns_id })
eq(tree.ns_id, ns_id)
end)
it("uses o.get_node_id if provided", function()
local node_d2 = Tree.Node({ key = "depth two" })
local node_d1 = Tree.Node({ key = "depth one" }, { node_d2 })
Tree({
bufnr = bufnr,
nodes = { node_d1 },
get_node_id = function(node)
return node.key
end,
})
eq(node_d1:get_id(), node_d1.key)
eq(node_d2:get_id(), node_d2.key)
end)
describe("default get_node_id", function()
it("returns id using n.id", function()
local node = Tree.Node({ id = "id", text = "text" })
Tree({ bufnr = bufnr, nodes = { node } })
eq(node:get_id(), "-id")
end)
it("returns id using parent_id + depth + n.text", function()
local node_d2 = Tree.Node({ text = { "depth two a", Text("depth two b") } })
local node_d1 = Tree.Node({ text = "depth one" }, { node_d2 })
Tree({ bufnr = bufnr, nodes = { node_d1 } })
eq(node_d1:get_id(), string.format("-%s-%s", node_d1:get_depth(), node_d1.text))
eq(
node_d2:get_id(),
string.format(
"%s-%s-%s",
node_d2:get_parent_id(),
node_d2:get_depth(),
table.concat({ node_d2.text[1], node_d2.text[2]:content() }, "-")
)
)
end)
it("returns id using random number", function()
math.randomseed(0)
local expected_id = "-" .. math.random()
math.randomseed(0)
local node = Tree.Node({})
Tree({ bufnr = bufnr, nodes = { node } })
eq(node:get_id(), expected_id)
end)
end)
it("uses o.prepare_node if provided", function()
local function prepare_node(node, parent_node)
if not parent_node then
return node.text
end
return parent_node.text .. ":" .. node.text
end
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, {
Tree.Node({ text = "b-1" }),
Tree.Node({ text = "b-2" }),
}),
Tree.Node({ text = "c" }),
}
nodes[2]:expand()
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
prepare_node = prepare_node,
})
tree:render()
h.assert_buf_lines(tree.bufnr, {
"a",
"b",
"b:b-1",
"b:b-2",
"c",
})
end)
describe("default prepare_node", function()
it("throws if missing n.text", function()
local nodes = {
Tree.Node({ txt = "a" }),
Tree.Node({ txt = "b" }),
Tree.Node({ txt = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
})
local ok, err = pcall(tree.render, tree)
eq(ok, false)
eq(type(err), "string")
end)
it("uses n.text", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = { "b-1", "b-2" } }),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
})
tree:render()
h.assert_buf_lines(tree.bufnr, {
" a",
" b-1",
" b-2",
" c",
})
end)
it("renders arrow if children are present", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, {
Tree.Node({ text = "b-1" }),
Tree.Node({ text = { "b-2", "b-3" } }),
}),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
})
tree:render()
h.assert_buf_lines(tree.bufnr, {
" a",
" b",
" c",
})
nodes[2]:expand()
tree:render()
h.assert_buf_lines(tree.bufnr, {
" a",
" b",
" b-1",
" b-2",
" b-3",
" c",
})
end)
end)
describe("method :get_node", function()
it("can get node under cursor", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
})
tree:render()
local linenr = 3
vim.api.nvim_win_set_cursor(winid, { linenr, 0 })
eq({ tree:get_node() }, { nodes[3], linenr, linenr })
end)
it("can get node with id", function()
local b_node_children = {
Tree.Node({ text = "b-1" }),
Tree.Node({ text = { "b-2", "b-3" } }),
}
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, b_node_children),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return type(node.text) == "table" and table.concat(node.text, "-") or node.text
end,
})
tree:render()
eq({ tree:get_node("b") }, { nodes[2], 2, 2 })
tree:get_node("b"):expand()
tree:render()
eq({ tree:get_node("b-2-b-3") }, { b_node_children[2], 4, 5 })
end)
it("can get node on linenr", function()
local b_node_children = {
Tree.Node({ id = "b-1-b-2", text = { "b-1", "b-2" } }),
}
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, b_node_children),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
})
tree:render()
eq({ tree:get_node(1) }, { nodes[1], 1, 1 })
tree:get_node(2):expand()
tree:render()
eq({ tree:get_node(3) }, { b_node_children[1], 3, 4 })
eq({ tree:get_node(4) }, { b_node_children[1], 3, 4 })
end)
end)
describe("method :get_nodes", function()
it("can get nodes at root", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, {
Tree.Node({ text = "b-1" }),
}),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return node.text
end,
})
eq(tree:get_nodes(), nodes)
end)
it("can get nodes under parent node", function()
local child_nodes = {
Tree.Node({ text = "b-1" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, child_nodes),
},
get_node_id = function(node)
return node.text
end,
})
eq(tree:get_nodes("b"), child_nodes)
end)
end)
describe("method :add_node", function()
it("throw if invalid parent_id", function()
local tree = Tree({
bufnr = bufnr,
nodes = {
Tree.Node({ text = "x" }),
},
})
local ok, err = pcall(tree.add_node, tree, Tree.Node({ text = "y" }), "invalid_parent_id")
eq(ok, false)
eq(type(err), "string")
end)
it("can add node at root", function()
local tree = Tree({
bufnr = bufnr,
nodes = {
Tree.Node({ text = "x" }),
},
})
tree:add_node(Tree.Node({ text = "y" }))
tree:render()
h.assert_buf_lines(tree.bufnr, {
" x",
" y",
})
tree:add_node(Tree.Node({ text = "z" }))
tree:render()
h.assert_buf_lines(tree.bufnr, {
" x",
" y",
" z",
})
end)
it("can add node under parent node", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, {
Tree.Node({ text = "b-1" }),
}),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return node.text
end,
})
tree:add_node(Tree.Node({ text = "b-2" }), "b")
tree:get_node("b"):expand()
tree:add_node(Tree.Node({ text = "c-1" }), "c")
tree:get_node("c"):expand()
tree:render()
h.assert_buf_lines(tree.bufnr, {
" a",
" b",
" b-1",
" b-2",
" c",
" c-1",
})
end)
end)
describe("method :set_nodes", function()
it("throw if invalid parent_id", function()
local tree = Tree({
bufnr = bufnr,
nodes = {
Tree.Node({ text = "x" }),
},
})
local ok, err = pcall(tree.set_nodes, tree, {}, "invalid_parent_id")
eq(ok, false)
eq(type(err), "string")
end)
it("can set nodes at root", function()
local tree = Tree({
bufnr = bufnr,
nodes = {
Tree.Node({ text = "x" }),
},
})
tree:set_nodes({
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }),
})
tree:render()
h.assert_buf_lines(tree.bufnr, {
" a",
" b",
})
tree:set_nodes({
Tree.Node({ text = "c" }),
})
tree:render()
h.assert_buf_lines(tree.bufnr, {
" c",
})
end)
it("can set nodes under parent node", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, {
Tree.Node({ text = "b-1" }),
}),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return node.text
end,
})
tree:set_nodes({
Tree.Node({ text = "b-2" }),
}, "b")
tree:get_node("b"):expand()
tree:set_nodes({
Tree.Node({ text = "c-1" }),
Tree.Node({ text = "c-2" }),
}, "c")
tree:get_node("c"):expand()
tree:render()
h.assert_buf_lines(tree.bufnr, {
" a",
" b",
" b-2",
" c",
" c-1",
" c-2",
})
end)
end)
describe("method :remove_node", function()
it("can remove node w/o parent", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, {
Tree.Node({ text = "b-1" }),
}),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return node.text
end,
})
tree:remove_node("a")
tree:get_node("b"):expand()
tree:render()
eq(
vim.tbl_map(function(node)
return node:get_id()
end, tree:get_nodes()),
{ "b", "c" }
)
h.assert_buf_lines(tree.bufnr, {
" b",
" b-1",
" c",
})
end)
it("can remove node w/ parent", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, {
Tree.Node({ text = "b-1" }),
}),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return node.text
end,
})
tree:remove_node("b-1")
tree:render()
eq(tree:get_node("b"):get_child_ids(), {})
h.assert_buf_lines(tree.bufnr, {
" a",
" b",
" c",
})
end)
it("removes children nodes recursively", function()
local nodes = {
Tree.Node({ text = "a" }, {
Tree.Node({ text = "a-1" }, {
Tree.Node({ text = "a-1-x" }),
}),
}),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return node.text
end,
})
h.neq(tree:get_node("a"), nil)
h.neq(tree:get_node("a-1"), nil)
h.neq(tree:get_node("a-1-x"), nil)
tree:remove_node("a")
eq(tree:get_node("a"), nil)
eq(tree:get_node("a-1"), nil)
eq(tree:get_node("a-1-x"), nil)
end)
end)
describe("method :render", function()
it("handles unexpected case of missing node", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return node.text
end,
})
-- this should not happen normally
tree.nodes.by_id["a"] = nil
tree:render()
h.assert_buf_lines(tree.bufnr, {
" b",
" c",
})
end)
it("skips node if o.prepare_node returns nil", function()
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }),
Tree.Node({ text = "c" }),
}
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return node.text
end,
prepare_node = function(node)
if node:get_id() == "b" then
return nil
end
return node.text
end,
})
tree:render()
h.assert_buf_lines(tree.bufnr, {
"a",
"c",
})
end)
it("supports param linenr_start", function()
local b_node_children = {
Tree.Node({ text = "b-1" }),
Tree.Node({ text = "b-2" }),
}
local nodes = {
Tree.Node({ text = "a" }),
Tree.Node({ text = "b" }, b_node_children),
}
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
"NuiTreeTest",
"",
"NuiTreeTest",
})
local tree = Tree({
bufnr = bufnr,
nodes = nodes,
get_node_id = function(node)
return node.text
end,
})
tree:render(2)
h.assert_buf_lines(tree.bufnr, {
"NuiTreeTest",
" a",
" b",
"NuiTreeTest",
})
nodes[2]:expand()
tree:render()
h.assert_buf_lines(tree.bufnr, {
"NuiTreeTest",
" a",
" b",
" b-1",
" b-2",
"NuiTreeTest",
})
nodes[2]:collapse()
tree:render(3)
h.assert_buf_lines(tree.bufnr, {
"NuiTreeTest",
"",
" a",
" b",
"NuiTreeTest",
})
end)
end)
end)
describe("nui.tree.Node", function()
describe("method :has_children", function()
it("works before initialization", function()
local node_wo_children = Tree.Node({ text = "a" })
local node_w_children = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
eq(node_wo_children._initialized, false)
eq(node_wo_children:has_children(), false)
eq(node_w_children._initialized, false)
eq(type(node_w_children.__children), "table")
eq(node_w_children:has_children(), true)
end)
it("works after initialization", function()
local node_wo_children = Tree.Node({ text = "a" })
local node_w_children = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
Tree({
bufnr = vim.api.nvim_win_get_buf(vim.api.nvim_get_current_win()),
nodes = { node_wo_children, node_w_children },
})
eq(node_wo_children._initialized, true)
eq(node_wo_children:has_children(), false)
eq(node_w_children._initialized, true)
eq(type(node_w_children.__children), "nil")
eq(node_w_children:has_children(), true)
end)
end)
describe("method :expand", function()
it("returns true if not already expanded", function()
local node = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
eq(node:is_expanded(), false)
eq(node:expand(), true)
eq(node:is_expanded(), true)
end)
it("returns false if already expanded", function()
local node = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
node:expand()
eq(node:is_expanded(), true)
eq(node:expand(), false)
eq(node:is_expanded(), true)
end)
it("does work w/ zero child", function()
local node = Tree.Node({ text = "a" }, {})
eq(node:is_expanded(), false)
eq(node:expand(), true)
eq(node:is_expanded(), true)
end)
it("does not work w/o children", function()
local node = Tree.Node({ text = "a" })
eq(node:is_expanded(), false)
eq(node:expand(), false)
eq(node:is_expanded(), false)
end)
end)
describe("method :collapse", function()
it("returns true if not already collapsed", function()
local node = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
node:expand()
eq(node:is_expanded(), true)
eq(node:collapse(), true)
eq(node:is_expanded(), false)
end)
it("returns false if already collapsed", function()
local node = Tree.Node({ text = "b" }, { Tree.Node({ text = "b-1" }) })
eq(node:is_expanded(), false)
eq(node:collapse(), false)
eq(node:is_expanded(), false)
end)
it("does not work w/o children", function()
local node = Tree.Node({ text = "a" })
eq(node:is_expanded(), false)
eq(node:collapse(), false)
eq(node:is_expanded(), false)
end)
end)
end)