require 'bugabinga.cycle' describe('bugabinga.cycle', function() local function create_buffer(name) vim.api.nvim_command('new ' .. name) local bufnr = vim.api.nvim_get_current_buf() return bufnr end before_each(function() package.loaded['bugabinga.cycle'] = nil end) after_each(function() BugabingaCycle.cancel() for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_is_valid(bufnr) then pcall(vim.api.nvim_buf_delete, bufnr, { force = true }) end end if BugabingaCycle.history then for i = #BugabingaCycle.history, 1, -1 do BugabingaCycle.history[i] = nil end end end) describe('history tracking', function() it('should track buffers on BufEnter', function() local bufnr1 = create_buffer('test1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('test2.lua') vim.api.nvim_set_current_buf(bufnr2) local bufnr3 = create_buffer('test3.lua') vim.api.nvim_set_current_buf(bufnr3) assert.is_true(#BugabingaCycle.history >= 1) end) it('should skip unnamed buffers', function() vim.api.nvim_command('enew') local bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_set_current_buf(bufnr) for _, b in ipairs(BugabingaCycle.history) do assert.are_not_equal(bufnr, b) end end) it('should skip unlisted buffers', function() vim.api.nvim_command('new') local bufnr = vim.api.nvim_get_current_buf() vim.bo[bufnr].buflisted = false vim.api.nvim_set_current_buf(bufnr) for _, b in ipairs(BugabingaCycle.history) do assert.are_not_equal(bufnr, b) end end) it('should move revisited buffer to end of history', function() local bufnr1 = create_buffer('mruseq1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('mruseq2.lua') vim.api.nvim_set_current_buf(bufnr2) local bufnr3 = create_buffer('mruseq3.lua') vim.api.nvim_set_current_buf(bufnr3) local last = BugabingaCycle.history[#BugabingaCycle.history] assert.equal(bufnr3, last) end) it('should remove deleted buffers from history', function() local bufnr1 = create_buffer('del1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('del2.lua') vim.api.nvim_set_current_buf(bufnr2) local bufnr3 = create_buffer('del3.lua') vim.api.nvim_set_current_buf(bufnr3) vim.api.nvim_buf_delete(bufnr2, { force = true }) for _, b in ipairs(BugabingaCycle.history) do assert.are_not_equal(bufnr2, b) end end) end) describe('cycling', function() it('should open popup with buffer list when multiple buffers exist', function() local bufnr1 = create_buffer('cycle1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('cycle2.lua') vim.api.nvim_set_current_buf(bufnr2) local win_count_before = #vim.api.nvim_list_wins() BugabingaCycle.cycle(1) local win_count_after = #vim.api.nvim_list_wins() assert.is_true(win_count_after > win_count_before) BugabingaCycle.cancel() end) it('should open popup with current buffer included in list', function() local bufnr1 = create_buffer('single1.lua') vim.api.nvim_set_current_buf(bufnr1) local win_count_before = #vim.api.nvim_list_wins() BugabingaCycle.cycle(1) local win_count_after = #vim.api.nvim_list_wins() assert(win_count_after > win_count_before, 'popup should open') end) it('should cycle forward through buffer list', function() local bufnr1 = create_buffer('fwd1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('fwd2.lua') vim.api.nvim_set_current_buf(bufnr2) local bufnr3 = create_buffer('fwd3.lua') vim.api.nvim_set_current_buf(bufnr3) BugabingaCycle.cycle(1) assert.is_not_nil(BugabingaCycle.cancel) BugabingaCycle.cancel() end) it('should not crash when cycling second time with multiple buffers', function() local bufnr1 = create_buffer('multifwd1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('multifwd2.lua') vim.api.nvim_set_current_buf(bufnr2) local bufnr3 = create_buffer('multifwd3.lua') vim.api.nvim_set_current_buf(bufnr3) BugabingaCycle.cycle(1) local ok, err = pcall(BugabingaCycle.cycle, 1) assert(ok, 'second cycle should not error: ' .. tostring(err)) BugabingaCycle.cancel() end) it('should cycle backward through buffer list', function() local bufnr1 = create_buffer('bwd1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('bwd2.lua') vim.api.nvim_set_current_buf(bufnr2) BugabingaCycle.cycle(-1) assert.is_not_nil(BugabingaCycle.cancel) BugabingaCycle.cancel() end) it('should wrap from first to last when cycling forward', function() local bufnr1 = create_buffer('wrap1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('wrap2.lua') vim.api.nvim_set_current_buf(bufnr2) BugabingaCycle.cycle(1) BugabingaCycle.cancel() end) end) describe('cancel', function() it('should restore original buffer after cancel', function() local bufnr1 = create_buffer('rest1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('rest2.lua') vim.api.nvim_set_current_buf(bufnr2) local original = vim.api.nvim_get_current_buf() BugabingaCycle.cycle(1) BugabingaCycle.cancel() local restored = vim.api.nvim_get_current_buf() assert.equal(original, restored) end) it('should not crash when timer is already nil', function() local bufnr1 = create_buffer('nocancel1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('nocancel2.lua') vim.api.nvim_set_current_buf(bufnr2) BugabingaCycle.cancel() end) it('should stop and clear timer on cancel', function() local bufnr1 = create_buffer('timerclear1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('timerclear2.lua') vim.api.nvim_set_current_buf(bufnr2) BugabingaCycle.cycle(1) BugabingaCycle.cancel() end) end) describe('commit_switch', function() it('should switch to highlighted buffer after timer fires', function() local bufnr1 = create_buffer('commit1.lua') vim.api.nvim_set_current_buf(bufnr1) local bufnr2 = create_buffer('commit2.lua') vim.api.nvim_set_current_buf(bufnr2) BugabingaCycle.cycle(1) vim.wait(1100) local current = vim.api.nvim_get_current_buf() assert.equal(bufnr1, current) end) it('should not switch when no popup was opened', function() vim.api.nvim_command('enew') local bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_set_current_buf(bufnr) local original = vim.api.nvim_get_current_buf() BugabingaCycle.cycle(1) local current = vim.api.nvim_get_current_buf() assert.equal(original, current) end) end) describe('rendering', function() it('shorten_path should return basename for paths outside cwd', function() local short = '/a/b/c.lua' local result = BugabingaCycle.shorten_path(short, 50) assert.equal('c.lua', result) end) it('shorten_path should keep full relative path when it fits', function() local path = '/home/user/project/file.lua' local result = BugabingaCycle.shorten_path(path, 30) assert(#result <= 30, 'should fit: ' .. result .. ' (' .. #result .. ')') assert(result:find('file.lua', 1, true) ~= nil, 'basename must be present: ' .. result) end) it('shorten_path should return basename for paths outside cwd', function() local long = '/home/user/very/deep/nested/path/to/file.lua' local result = BugabingaCycle.shorten_path(long, 25) local basename = 'file.lua' assert.equal(basename, result, 'should return basename: ' .. result) end) it('shorten_path should truncate dirname, result may exceed max if basename is long', function() local path = '/home/user/very/deep/nested/path/to/file.lua' local result = BugabingaCycle.shorten_path(path, 15) local basename = vim.fn.fnamemodify(path, ':t') assert(result:find(basename, 1, true) ~= nil, 'basename should be present: ' .. result) assert(#result <= 20, 'should be shortened: ' .. result .. ' (' .. #result .. ')') end) it('get_icon should return a string', function() local icon, _ = BugabingaCycle.get_icon('test.lua') assert.is_string(icon) assert.is_true(#icon > 0) end) end) describe('module API', function() it('should export cycle, cancel, and history', function() assert.is_function(BugabingaCycle.cycle) assert.is_function(BugabingaCycle.cancel) assert.is_table(BugabingaCycle.history) end) end) end)