Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(output_panel): recreate window and term channel if panel closed manually #477

Merged
merged 1 commit into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions lua/neotest/consumers/output_panel/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ local init = function(client)
if partial then
return
end
if not chan then

local channel_is_valid = function(chan_id)
return chan_id and pcall(vim.api.nvim_chan_send, chan_id, "\n")
end

if not channel_is_valid(chan) then
chan = lib.ui.open_term(panel.win:buffer())
-- neovim sometimes adds random blank lines when creating a terminal buffer
nio.api.nvim_buf_set_option(panel.win:buffer(), "modifiable", true)
Expand All @@ -50,7 +55,11 @@ local init = function(client)
for file, _ in pairs(files_to_read) do
local output = lib.files.read(file)
local dos_newlines = string.find(output, "\r\n") ~= nil
nio.api.nvim_chan_send(chan, dos_newlines and output or output:gsub("\n", "\r\n"))
if not pcall(nio.api.nvim_chan_send, chan, dos_newlines and output or output:gsub("\n", "\r\n")) then
lib.notify(("Error sending output to term channel: %s"):format(chan), vim.log.levels.ERROR)
chan = nil
break
end
end
end
end
Expand Down
12 changes: 11 additions & 1 deletion lua/neotest/lib/window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,20 @@ function PersistentWindow:open()
end

function PersistentWindow:buffer()
if self._bufnr and vim.fn.bufexists(self._bufnr) == 1 then
if
self._bufnr
and nio.api.nvim_buf_is_valid(self._bufnr)
and nio.api.nvim_buf_is_loaded(self._bufnr)
then
return self._bufnr
end

for _, bufnr in ipairs(nio.api.nvim_list_bufs()) do
if nio.api.nvim_buf_get_name(bufnr):find(self.name, 1, true) then
nio.api.nvim_buf_delete(bufnr, { force = true })
end
end

self._bufnr = nio.api.nvim_create_buf(false, true)
nio.api.nvim_buf_set_name(self._bufnr, self.name)
for k, v in pairs(self._bufopts) do
Expand Down
192 changes: 192 additions & 0 deletions tests/unit/consumers/output_panel_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
local neotest = require("neotest")

local nio = require("nio")
local a = nio.tests

local stub = require("luassert.stub")
local Tree = require("neotest.types").Tree
local lib = require("neotest.lib")

local NeotestClient = require("neotest.client")
local AdapterGroup = require("neotest.adapters")

describe("neotest consumer - output_panel", function()
---@type neotest.Client
local client

---@type neotest.Adapter
local mock_adapter
local mock_strategy
local exit_future_1, exit_future_2

local dir = vim.loop.cwd()
local files
local dirs = { dir }
local notify
local notify_msg = ''

---@return neotest.Tree
local get_pos = function(...)
---@diagnostic disable-next-line
return client:get_position(...)
end

before_each(function()
dirs = { dir }
files = { dir .. "/test_file_1", dir .. "/test_file_2" }

notify = function(message, level)
notify_msg = message
end

stub(lib, "notify", notify)
stub(lib.files, "find", files)
stub(lib.files, "read", "Test results - passed and failed\r\n")
stub(lib.files, "is_dir", function(path)
return vim.tbl_contains(dirs, path)
end)
stub(lib.files, "exists", function(path)
return path ~= ""
end)

exit_future_1, exit_future_2 = nio.control.future(), nio.control.future()

---@diagnostic disable-next-line: missing-fields
mock_adapter = {
name = "adapter",

is_test_file = function(file_path)
return file_path ~= "" and not vim.endswith(file_path, lib.files.sep)
end,

root = function()
return dir
end,

discover_positions = function(file_path)
return Tree.from_list({
{ id = file_path, type = "file", path = file_path, name = file_path },
{
{
id = file_path .. "::namespace",
type = "namespace",
path = file_path,
name = "namespace",
range = { 5, 0, 50, 0 },
},
{
id = file_path .. "::test_a",
type = "test",
path = file_path,
name = "test_a",
range = { 10, 0, 20, 50 },
},
},
}, function(pos)
return pos.id
end)
end,

build_spec = function()
return { strategy = { output = "not_a_file" } }
end,

results = function(_, _, tree)
return {}
end,
}

mock_strategy = function(spec)
return {
is_complete = function()
return true
end,

output = function()
return type(spec.strategy) == "table" and spec.strategy.output or "not_a_file"
end,

stop = function()
exit_future_1.set()
exit_future_2.set()
end,

result = function()
if not exit_future_1.is_set() then
exit_future_1.wait()
else
exit_future_2.wait()
end
return type(spec.strategy) == "table" and spec.strategy.exit_code or 0
end,
}
end

client = NeotestClient(AdapterGroup())
---@diagnostic disable-next-line
neotest.setup({ adapters = { mock_adapter }, output_panel = { enabled = true } })

require("neotest.consumers.output_panel")(client)
---@diagnostic disable-next-line
client.listeners.results = { output_panel = client.listeners.results }
end)

after_each(function()
lib.files.find:revert()
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
vim.api.nvim_buf_delete(buf, { force = true })
end
notify_msg = ''
end)

describe("user forcefully closes the panel", function()
local panel_bufnr = function()
return vim.tbl_filter(function(bufnr)
return nio.api.nvim_buf_get_name(bufnr):match("Neotest Output Panel")
end, nio.api.nvim_list_bufs())[1]
end

before_each(function()
neotest.output_panel.open()
end)

a.it("recreates terminal session if term channel is invalid", function()
local tree = get_pos(dir .. "/test_file_1")

nio.run(function()
client:run_tree(tree, { strategy = mock_strategy })
end)
exit_future_1.set()

nio.api.nvim_buf_delete(panel_bufnr(), { force = true })
neotest.output_panel.open()

nio.run(function()
assert.has_no_error(function()
client:run_tree(tree, { strategy = mock_strategy })
end)
assert.is_not.matches('Error sending output to term channel:', notify_msg)
end)
exit_future_2.set()
end)

it("recreates panel buffer if it was closed", function()
vim.api.nvim_buf_delete(panel_bufnr(), { force = true })

assert.has_no_error(function()
neotest.output_panel.open()
end)
end)

it("deletes panel buffer if it already exists with the same name", function()
vim.api.nvim_buf_delete(panel_bufnr(), { force = true })

local buf = vim.api.nvim_create_buf(true, false)
vim.api.nvim_buf_set_name(buf, "Neotest Output Panel")

assert.has_no_error(function()
neotest.output_panel.open()
end)
end)
end)
end)