From d4adc9316ee3bb1d7467bb0053d9666bb8ab4534 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sat, 20 Sep 2025 16:38:46 -0400 Subject: [PATCH] feat(test): extmark tests --- spec/extmark_spec.lua | 277 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 spec/extmark_spec.lua diff --git a/spec/extmark_spec.lua b/spec/extmark_spec.lua new file mode 100644 index 0000000..3ecd03e --- /dev/null +++ b/spec/extmark_spec.lua @@ -0,0 +1,277 @@ +describe('extmarks', function() + local spec_helper = require('spec.spec_helper') + local highlight + + before_each(function() + spec_helper.setup() + highlight = require('cp.highlight') + end) + + after_each(function() + spec_helper.teardown() + end) + + describe('buffer deletion', function() + it('clears namespace when buffer is deleted', function() + local bufnr = 1 + local namespace = 100 + local mock_clear = spy.on(vim.api, 'nvim_buf_clear_namespace') + local mock_delete = stub(vim.api, 'nvim_buf_delete') + + mock_delete.returns(true) + + highlight.apply_highlights(bufnr, { + { + line = 0, + col_start = 0, + col_end = 5, + highlight_group = 'CpDiffAdded', + }, + }, namespace) + + vim.api.nvim_buf_delete(bufnr, { force = true }) + + assert.spy(mock_clear).was_called_with(bufnr, namespace, 0, -1) + mock_clear:revert() + mock_delete:revert() + end) + + it('handles buffer deletion failure', function() + local bufnr = 1 + local namespace = 100 + local mock_clear = spy.on(vim.api, 'nvim_buf_clear_namespace') + local mock_delete = stub(vim.api, 'nvim_buf_delete') + + mock_delete.throws('Buffer deletion failed') + + highlight.apply_highlights(bufnr, { + { + line = 0, + col_start = 0, + col_end = 5, + highlight_group = 'CpDiffAdded', + }, + }, namespace) + + local success = pcall(vim.api.nvim_buf_delete, bufnr, { force = true }) + + assert.is_false(success) + assert.spy(mock_clear).was_called_with(bufnr, namespace, 0, -1) + + mock_clear:revert() + mock_delete:revert() + end) + + it('handles invalid buffer', function() + local bufnr = 999 + local namespace = 100 + local mock_clear = spy.on(vim.api, 'nvim_buf_clear_namespace') + local mock_extmark = spy.on(vim.api, 'nvim_buf_set_extmark') + + mock_clear.throws('Invalid buffer') + mock_extmark.throws('Invalid buffer') + + local success = pcall(highlight.apply_highlights, bufnr, { + { + line = 0, + col_start = 0, + col_end = 5, + highlight_group = 'CpDiffAdded', + }, + }, namespace) + + assert.is_false(success) + + mock_clear:revert() + mock_extmark:revert() + end) + end) + + describe('namespace isolation', function() + it('creates unique namespaces', function() + local mock_create = stub(vim.api, 'nvim_create_namespace') + mock_create.on_call_with('cp_diff_highlights').returns(100) + mock_create.on_call_with('cp_test_list').returns(200) + mock_create.on_call_with('cp_ansi_highlights').returns(300) + + local diff_ns = highlight.create_namespace() + local test_ns = vim.api.nvim_create_namespace('cp_test_list') + local ansi_ns = vim.api.nvim_create_namespace('cp_ansi_highlights') + + assert.equals(100, diff_ns) + assert.equals(200, test_ns) + assert.equals(300, ansi_ns) + + mock_create:revert() + end) + + it('clears specific namespace without affecting others', function() + local bufnr = 1 + local ns1 = 100 + local ns2 = 200 + local mock_clear = spy.on(vim.api, 'nvim_buf_clear_namespace') + + highlight.apply_highlights(bufnr, { + { line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' }, + }, ns1) + + highlight.apply_highlights(bufnr, { + { line = 1, col_start = 0, col_end = 3, highlight_group = 'CpDiffRemoved' }, + }, ns2) + + assert.spy(mock_clear).was_called_with(bufnr, ns1, 0, -1) + assert.spy(mock_clear).was_called_with(bufnr, ns2, 0, -1) + assert.spy(mock_clear).was_called(2) + + mock_clear:revert() + end) + end) + + describe('multiple updates', function() + it('clears previous extmarks on each update', function() + local bufnr = 1 + local namespace = 100 + local mock_clear = spy.on(vim.api, 'nvim_buf_clear_namespace') + local mock_extmark = spy.on(vim.api, 'nvim_buf_set_extmark') + + highlight.apply_highlights(bufnr, { + { line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' }, + }, namespace) + + highlight.apply_highlights(bufnr, { + { line = 1, col_start = 0, col_end = 3, highlight_group = 'CpDiffRemoved' }, + }, namespace) + + highlight.apply_highlights(bufnr, { + { line = 2, col_start = 0, col_end = 7, highlight_group = 'CpDiffAdded' }, + }, namespace) + + assert.spy(mock_clear).was_called(3) + assert.spy(mock_clear).was_called_with(bufnr, namespace, 0, -1) + assert.spy(mock_extmark).was_called(3) + + mock_clear:revert() + mock_extmark:revert() + end) + + it('handles empty highlights', function() + local bufnr = 1 + local namespace = 100 + local mock_clear = spy.on(vim.api, 'nvim_buf_clear_namespace') + local mock_extmark = spy.on(vim.api, 'nvim_buf_set_extmark') + + highlight.apply_highlights(bufnr, { + { line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' }, + }, namespace) + + highlight.apply_highlights(bufnr, {}, namespace) + + assert.spy(mock_clear).was_called(2) + assert.spy(mock_extmark).was_called(1) + + mock_clear:revert() + mock_extmark:revert() + end) + + it('skips invalid highlights', function() + local bufnr = 1 + local namespace = 100 + local mock_clear = spy.on(vim.api, 'nvim_buf_clear_namespace') + local mock_extmark = spy.on(vim.api, 'nvim_buf_set_extmark') + + highlight.apply_highlights(bufnr, { + { line = 0, col_start = 5, col_end = 5, highlight_group = 'CpDiffAdded' }, + { line = 1, col_start = 7, col_end = 3, highlight_group = 'CpDiffAdded' }, + { line = 2, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' }, + }, namespace) + + assert.spy(mock_clear).was_called_with(bufnr, namespace, 0, -1) + assert.spy(mock_extmark).was_called(1) + assert.spy(mock_extmark).was_called_with(bufnr, namespace, 2, 0, { + end_col = 5, + hl_group = 'CpDiffAdded', + priority = 100, + }) + + mock_clear:revert() + mock_extmark:revert() + end) + end) + + describe('error handling', function() + it('fails when clear_namespace fails', function() + local bufnr = 1 + local namespace = 100 + local mock_clear = stub(vim.api, 'nvim_buf_clear_namespace') + local mock_extmark = spy.on(vim.api, 'nvim_buf_set_extmark') + + mock_clear.throws('Namespace clear failed') + + local success = pcall(highlight.apply_highlights, bufnr, { + { line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' }, + }, namespace) + + assert.is_false(success) + assert.spy(mock_extmark).was_not_called() + + mock_clear:revert() + mock_extmark:revert() + end) + + it('fails when extmark creation fails', function() + local bufnr = 1 + local namespace = 100 + local mock_clear = spy.on(vim.api, 'nvim_buf_clear_namespace') + local mock_extmark = stub(vim.api, 'nvim_buf_set_extmark') + + mock_extmark.throws('Extmark failed') + + local success = pcall(highlight.apply_highlights, bufnr, { + { line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' }, + }, namespace) + + assert.spy(mock_clear).was_called_with(bufnr, namespace, 0, -1) + assert.is_false(success) + + mock_clear:revert() + mock_extmark:revert() + end) + end) + + describe('buffer lifecycle', function() + it('manages extmarks through full lifecycle', function() + local mock_create_buf = stub(vim.api, 'nvim_create_buf') + local mock_delete_buf = stub(vim.api, 'nvim_buf_delete') + local mock_clear = spy.on(vim.api, 'nvim_buf_clear_namespace') + local mock_extmark = spy.on(vim.api, 'nvim_buf_set_extmark') + + local bufnr = 42 + local namespace = 100 + + mock_create_buf.returns(bufnr) + mock_delete_buf.returns(true) + + local buf = vim.api.nvim_create_buf(false, true) + assert.equals(bufnr, buf) + + highlight.apply_highlights(bufnr, { + { line = 0, col_start = 0, col_end = 5, highlight_group = 'CpDiffAdded' }, + }, namespace) + + highlight.apply_highlights(bufnr, { + { line = 1, col_start = 0, col_end = 3, highlight_group = 'CpDiffRemoved' }, + }, namespace) + + vim.api.nvim_buf_delete(bufnr, { force = true }) + + assert.spy(mock_clear).was_called(2) + assert.spy(mock_extmark).was_called(2) + assert.stub(mock_delete_buf).was_called_with(bufnr, { force = true }) + + mock_create_buf:revert() + mock_delete_buf:revert() + mock_clear:revert() + mock_extmark:revert() + end) + end) +end)