describe('popupmenu.on_show', function() before_each(function() package.loaded['bugabinga.cmdline.popupmenu'] = nil end) after_each(function() local ok, popupmenu = pcall(require, 'bugabinga.cmdline.popupmenu') if ok then popupmenu.on_hide() end end) it('handles empty items', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' local ok = pcall(function() popupmenu.on_show({}, -1, 0, 0, -1) end) assert.is_true(ok) end) it('creates window with items', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' local initial_win_count = #vim.api.nvim_list_wins() popupmenu.on_show({{'foo', 'Variable', ''}, {'bar', 'Function', ''}}, 0, 0, 0, -1) local wins = vim.api.nvim_list_wins() assert.equal(initial_win_count + 1, #wins, 'should create a new window') local new_win = wins[#wins] local config = vim.api.nvim_win_get_config(new_win) assert(type(config) == 'table', 'window config should be a table') local buf = vim.api.nvim_win_get_buf(new_win) local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) assert(#lines > 0, 'buffer should have lines') end) it('hides safely when no window exists', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' local ok = pcall(function() popupmenu.on_hide() popupmenu.on_hide() end) assert.is_true(ok) end) end) describe('popupmenu.on_select', function() before_each(function() package.loaded['bugabinga.cmdline.popupmenu'] = nil end) after_each(function() local ok, popupmenu = pcall(require, 'bugabinga.cmdline.popupmenu') if ok then popupmenu.on_hide() end end) it('does not error without window', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' local ok = pcall(function() popupmenu.on_select(0) end) assert.is_true(ok) end) end) describe('popupmenu buffer cleanup', function() before_each(function() package.loaded['bugabinga.cmdline.popupmenu'] = nil end) after_each(function() local ok, popupmenu = pcall(require, 'bugabinga.cmdline.popupmenu') if ok then popupmenu.on_hide() end end) it('deletes buffer after on_hide', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' local initial_buf_count = #vim.api.nvim_list_bufs() popupmenu.on_show({{'foo', 'Variable', ''}}, 0, 0, 0, -1) local buf_count_after_show = #vim.api.nvim_list_bufs() assert(buf_count_after_show > initial_buf_count, 'should create a new buffer') local wins_before_hide = #vim.api.nvim_list_wins() popupmenu.on_hide() local wins_after_hide = #vim.api.nvim_list_wins() assert(wins_after_hide < wins_before_hide, 'window should be hidden') assert(buf_count_after_show > #vim.api.nvim_list_bufs(), 'buffer should be deleted') local ok = pcall(popupmenu.on_hide) assert(ok, 'on_hide should be idempotent') end) end) describe('popupmenu highlights', function() before_each(function() package.loaded['bugabinga.cmdline.popupmenu'] = nil package.loaded['bugabinga.cmdline.render'] = nil package.loaded['bugabinga.cmdline.position'] = nil end) after_each(function() local ok, popupmenu = pcall(require, 'bugabinga.cmdline.popupmenu') if ok then popupmenu.on_hide() end end) local function find_buf() local wins = vim.api.nvim_list_wins() for _, win in ipairs(wins) do local config = vim.api.nvim_win_get_config(win) if config.zindex and config.zindex == 201 then return vim.api.nvim_win_get_buf(win) end end return nil end local function get_extmarks(buf) local ns = vim.api.nvim_create_namespace 'bugabinga.cmdline.popupmenu' return vim.api.nvim_buf_get_extmarks(buf, ns, 0, -1, { details = true }) end it('applies PmenuMatch extmark when prefix matches abbr', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' popupmenu.on_show({{'foobar', '', ''}}, 0, 0, 0, -1, 'foo') local buf = find_buf() assert(buf ~= nil, 'popupmenu buffer should exist') local extmarks = get_extmarks(buf) local match_marks = vim.iter(extmarks):filter(function(m) return m[4].hl_group == 'PmenuMatch' end):totable() assert.equal(1, #match_marks, 'should have one PmenuMatch extmark') assert.equal(0, match_marks[1][3], 'PmenuMatch should start at col 0') assert.equal(3, match_marks[1][4].end_col, 'PmenuMatch should span 3 bytes (foo)') end) it('applies PmenuKind extmark over kind icon', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' popupmenu.on_show({{'bar', 'Function', ''}}, 0, 0, 0, -1, '') local buf = find_buf() assert.is_not_nil(buf) local extmarks = get_extmarks(buf) local kind_marks = vim.iter(extmarks):filter(function(m) return m[4].hl_group == 'PmenuKind' end):totable() assert.equal(1, #kind_marks, 'should have one PmenuKind extmark') assert.equal(0, kind_marks[1][3], 'PmenuKind should start at col 0') assert(kind_marks[1][4].end_col > 1, 'PmenuKind should span icon + space') end) it('applies no extmarks when prefix empty and no kind', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' popupmenu.on_show({{'baz', '', ''}}, 0, 0, 0, -1, '') local buf = find_buf() assert.is_not_nil(buf) local extmarks = get_extmarks(buf) assert.equal(0, #extmarks, 'should have no extmarks') end) it('applies both PmenuMatch and PmenuKind when both apply', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' local icon = require 'std.icon' popupmenu.on_show({{'foobar', 'Variable', ''}}, 0, 0, 0, -1, 'foo') local buf = find_buf() assert.is_not_nil(buf) local extmarks = get_extmarks(buf) local hl_groups = vim.iter(extmarks):map(function(m) return m[4].hl_group end):totable() assert(vim.list_contains(hl_groups, 'PmenuMatch'), 'should have PmenuMatch') assert(vim.list_contains(hl_groups, 'PmenuKind'), 'should have PmenuKind') end) it('applies no PmenuMatch when prefix does not match abbr', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' popupmenu.on_show({{'bar', '', ''}}, 0, 0, 0, -1, 'foo') local buf = find_buf() assert.is_not_nil(buf) local extmarks = get_extmarks(buf) local match_marks = vim.iter(extmarks):filter(function(m) return m[4].hl_group == 'PmenuMatch' end):totable() assert.equal(0, #match_marks, 'should have no PmenuMatch when prefix does not match') end) it('offsets PmenuMatch by kind icon when kind present', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' popupmenu.on_show({{'foobar', 'Function', ''}}, 0, 0, 0, -1, 'foo') local buf = find_buf() assert.is_not_nil(buf) local extmarks = get_extmarks(buf) local match_marks = vim.iter(extmarks):filter(function(m) return m[4].hl_group == 'PmenuMatch' end):totable() assert.equal(1, #match_marks) local kind_marks = vim.iter(extmarks):filter(function(m) return m[4].hl_group == 'PmenuKind' end):totable() assert.equal(1, #kind_marks, 'should have a PmenuKind extmark') assert.equal(kind_marks[1][4].end_col, match_marks[1][3], 'PmenuMatch should start where PmenuKind ended') assert.equal(match_marks[1][3] + 3, match_marks[1][4].end_col, 'PmenuMatch should span 3 bytes') end) it('highlights multiple items each with their own match extmark', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' popupmenu.on_show({{'foo', '', ''}, {'fbar', '', ''}}, 0, 0, 0, -1, 'f') local buf = find_buf() assert.is_not_nil(buf) local extmarks = get_extmarks(buf) local match_marks = vim.iter(extmarks):filter(function(m) return m[4].hl_group == 'PmenuMatch' end):totable() assert.equal(2, #match_marks, 'each item should have its own PmenuMatch extmark') end) it('does not error when prefix is nil', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' local ok = pcall(function() popupmenu.on_show({{'foo', '', ''}}, 0, 0, 0, -1) end) assert(ok, 'should not error with nil prefix') local buf = find_buf() assert.is_not_nil(buf) local extmarks = get_extmarks(buf) local match_marks = vim.iter(extmarks):filter(function(m) return m[4].hl_group == 'PmenuMatch' end):totable() assert.equal(0, #match_marks, 'should have no PmenuMatch with nil/empty prefix') end) end) describe('popupmenu scrollbar', function() before_each(function() package.loaded['bugabinga.cmdline.popupmenu'] = nil package.loaded['bugabinga.cmdline.render'] = nil package.loaded['bugabinga.cmdline.position'] = nil end) after_each(function() local ok, popupmenu = pcall(require, 'bugabinga.cmdline.popupmenu') if ok then popupmenu.on_hide() end end) local function find_buf() local wins = vim.api.nvim_list_wins() for _, win in ipairs(wins) do local config = vim.api.nvim_win_get_config(win) if config.zindex and config.zindex == 201 then return vim.api.nvim_win_get_buf(win) end end return nil end local function get_scroll_extmarks(buf) if not buf or not vim.api.nvim_buf_is_valid(buf) then return {} end local ns = vim.api.nvim_create_namespace 'bugabinga.cmdline.popupmenu' local marks = vim.api.nvim_buf_get_extmarks(buf, ns, 0, -1, { details = true }) return vim.iter(marks):filter(function(m) local vt = m[4].virt_text return vt and (#vt > 0) end):totable() end it('no scrollbar when items fit in viewport', function() local popupmenu = require 'bugabinga.cmdline.popupmenu' popupmenu.on_show({{'a','',''}, {'b','',''}, {'c','',''}}, 0, 0, 0, -1, '') local buf = find_buf() assert(buf ~= nil, 'popupmenu should exist') local scroll_marks = get_scroll_extmarks(buf) assert.equal(0, #scroll_marks, 'no scrollbar when items fit (pumheight unlimited = all visible)') end) end)