Meh I'll figure out submodules later
This commit is contained in:
parent
4ca9d44a90
commit
8cb281f436
352 changed files with 66107 additions and 0 deletions
|
|
@ -0,0 +1,285 @@
|
|||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
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()
|
||||
vim.wait(100, function() end)
|
||||
|
||||
input:unmount()
|
||||
input:unmount()
|
||||
input:unmount()
|
||||
|
||||
vim.fn.wait(200, function()
|
||||
return done > 1
|
||||
end)
|
||||
|
||||
eq(done, 1)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
2026
.config/nvim/pack/tree/start/nui.nvim/tests/nui/layout/init_spec.lua
Normal file
2026
.config/nvim/pack/tree/start/nui.nvim/tests/nui/layout/init_spec.lua
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,620 @@
|
|||
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)
|
||||
|
||||
it("can truncate content longer than max_width w/ multi-byte chars", function()
|
||||
menu = Menu(popup_options, {
|
||||
lines = {
|
||||
Menu.item("中文长度测试"),
|
||||
Menu.item("Test中英文测试"),
|
||||
Menu.item("Long Long Group"),
|
||||
},
|
||||
max_width = 11,
|
||||
})
|
||||
|
||||
menu:mount()
|
||||
|
||||
h.assert_buf_lines(menu.bufnr, {
|
||||
"中文长度测…",
|
||||
"Test中英文…",
|
||||
"Long Long …",
|
||||
})
|
||||
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)
|
||||
|
|
@ -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)
|
||||
File diff suppressed because it is too large
Load diff
1230
.config/nvim/pack/tree/start/nui.nvim/tests/nui/popup/init_spec.lua
Normal file
1230
.config/nvim/pack/tree/start/nui.nvim/tests/nui/popup/init_spec.lua
Normal file
File diff suppressed because it is too large
Load diff
1006
.config/nvim/pack/tree/start/nui.nvim/tests/nui/split/init_spec.lua
Normal file
1006
.config/nvim/pack/tree/start/nui.nvim/tests/nui/split/init_spec.lua
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue