require 'bugabinga.d2' local D2_STUB_OUTPUT = [[ +-------+ | hello | +-------+ ]] local mock_d2_system = function(output, code) local original_system = vim.system rawset(vim, 'system', function(cmd, opts) local handle = { wait = function() return { code = code or 0, stdout = output or '', stderr = '' } end, } return handle end) return function() rawset(vim, 'system', original_system) end end local create_d2_buffer = function(name, lines) vim.cmd(('edit! %s'):format(name)) local bufnr = vim.api.nvim_get_current_buf() if lines then vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) end return bufnr end local cleanup_preview = function() local pbnr = vim.fn.bufnr('D2_ASCII_PREVIEW') if pbnr ~= -1 and vim.api.nvim_buf_is_valid(pbnr) then pcall(vim.api.nvim_buf_delete, pbnr, { force = true }) end end local cleanup_d2_state = function() cleanup_preview() pcall(vim.api.nvim_del_augroup, 'd2_preview_autosave') pcall(vim.api.nvim_del_augroup, 'd2_validate_autosave') pcall(vim.api.nvim_del_augroup, 'd2_preview_keymaps') pcall(vim.api.nvim_del_user_command, 'D2Preview') pcall(vim.api.nvim_del_user_command, 'D2PreviewToggle') pcall(vim.api.nvim_del_user_command, 'D2Validate') pcall(vim.api.nvim_del_user_command, 'D2ValidateToggle') end describe('bugabinga.d2', function() before_each(function() package.loaded['bugabinga.d2'] = nil for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].filetype ~= 'qf' then pcall(vim.api.nvim_buf_delete, bufnr, { force = true }) end end cleanup_d2_state() end) after_each(function() cleanup_d2_state() end) describe('module API', function() it('should export preview_toggle', function() assert.is_function(BugabingaD2.preview_toggle) end) it('should export preview_update', function() assert.is_function(BugabingaD2.preview_update) end) it('should export preview_copy', function() assert.is_function(BugabingaD2.preview_copy) end) it('should export validate', function() assert.is_function(BugabingaD2.validate) end) it('should export validate_toggle', function() assert.is_function(BugabingaD2.validate_toggle) end) end) describe('user commands', function() it('should register D2Preview command', function() require 'bugabinga.d2' local ok = pcall(function() vim.cmd 'command D2Preview' end) assert(ok, 'D2Preview command should exist') end) it('should register D2PreviewToggle command', function() require 'bugabinga.d2' local ok = pcall(function() vim.cmd 'command D2PreviewToggle' end) assert(ok, 'D2PreviewToggle command should exist') end) it('should register D2Validate command without error', function() require 'bugabinga.d2' local ok, err = pcall(function() vim.cmd 'D2Validate' end) assert(ok, 'D2Validate should not error: ' .. tostring(err)) end) it('should register D2ValidateToggle command without error', function() require 'bugabinga.d2' local ok, err = pcall(function() vim.cmd 'D2ValidateToggle' end) assert(ok, 'D2ValidateToggle should not error: ' .. tostring(err)) end) end) describe('validate', function() it('should handle validate on unnamed buffer gracefully', function() local restore = mock_d2_system('', 0) require 'bugabinga.d2' vim.cmd 'enew' vim.bo.filetype = 'd2' local ok, err = pcall(BugabingaD2.validate) restore() assert(ok, 'validate should not crash on unnamed buffer: ' .. tostring(err)) end) it('should clear previous diagnostics on re-validate', function() require 'bugabinga.d2' local bufnr = create_d2_buffer('clear_test.d2', { 'x -> y' }) vim.diagnostic.reset(nil, bufnr) local diag_count = #vim.diagnostic.get(bufnr) assert.equal(0, diag_count) end) end) describe('validate_toggle', function() it('should toggle auto-validate without error', function() require 'bugabinga.d2' local ok, err = pcall(BugabingaD2.validate_toggle) assert(ok, 'validate_toggle should not error: ' .. tostring(err)) ok, err = pcall(BugabingaD2.validate_toggle) assert(ok, 'validate_toggle should not error on second call: ' .. tostring(err)) end) end) describe('autocmds', function() it('should create d2_preview_autosave autocmds', function() require 'bugabinga.d2' local autocmds = vim.api.nvim_get_autocmds { group = 'd2_preview_autosave' } assert(#autocmds > 0, 'd2_preview_autosave autocmds should exist') end) it('should create d2_validate_autosave autocmds', function() require 'bugabinga.d2' local autocmds = vim.api.nvim_get_autocmds { group = 'd2_validate_autosave' } assert(#autocmds > 0, 'd2_validate_autosave autocmds should exist') end) it('should scope preview keymaps to the event buffer', function() require 'bugabinga.d2' local preview_buf = vim.api.nvim_create_buf(false, true) local autocmd = vim.api.nvim_get_autocmds { group = 'd2_preview_keymaps' }[1] autocmd.callback { buf = preview_buf } local keymaps = vim.api.nvim_buf_get_keymap(preview_buf, 'n') assert.is_true(vim.iter(keymaps):any(function(keymap) return keymap.lhs == 'q' end)) end) end) describe('parse_validate_output', function() it('should parse line:col error format', function() local stderr = 'err: /path/file.d2:27:11: unexpected text after map\n' local results = BugabingaD2._parse_validate_output(stderr) assert.is_table(results) assert.is_true(#results >= 1) assert.equal(26, results[1].lnum) assert.equal(10, results[1].col) end) it('should parse error with URL prefix', function() local stderr = 'err: oss.terrastruct.com/d2/d2cli.validateCmd: 27:9: unexpected text after map\n' local results = BugabingaD2._parse_validate_output(stderr) assert.is_table(results) assert.is_true(#results >= 1) assert.equal(26, results[1].lnum) assert.equal(8, results[1].col) end) it('should return empty table for no errors', function() local stderr = '' local results = BugabingaD2._parse_validate_output(stderr) assert.is_table(results) assert.equal(0, #results) end) end) end)