u.nvim/spec/renderer_spec.lua
Jonathan Apodaca 97d3ba3620
All checks were successful
NeoVim tests / code-quality (push) Successful in 2m5s
add Renderer:get_tag_bounds; add tests; cleanup
2025-07-22 23:24:16 -06:00

263 lines
7.5 KiB
Lua

local R = require 'u.renderer'
local withbuf = loadfile './spec/withbuf.lua'()
local function getlines() return vim.api.nvim_buf_get_lines(0, 0, -1, true) end
describe('Renderer', function()
it('should render text in an empty buffer', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render { 'hello', ' ', 'world' }
assert.are.same(getlines(), { 'hello world' })
end)
end)
it('should result in the correct text after repeated renders', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render { 'hello', ' ', 'world' }
assert.are.same(getlines(), { 'hello world' })
r:render { 'goodbye', ' ', 'world' }
assert.are.same(getlines(), { 'goodbye world' })
r:render { 'hello', ' ', 'universe' }
assert.are.same(getlines(), { 'hello universe' })
end)
end)
it('should handle tags correctly', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render {
R.h('text', { hl = 'HighlightGroup' }, 'hello '),
R.h('text', { hl = 'HighlightGroup' }, 'world'),
}
assert.are.same(getlines(), { 'hello world' })
end)
end)
it('should reconcile added lines', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render { 'line 1', '\n', 'line 2' }
assert.are.same(getlines(), { 'line 1', 'line 2' })
-- Add a new line:
r:render { 'line 1', '\n', 'line 2\n', 'line 3' }
assert.are.same(getlines(), { 'line 1', 'line 2', 'line 3' })
end)
end)
it('should reconcile deleted lines', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render { 'line 1', '\nline 2', '\nline 3' }
assert.are.same(getlines(), { 'line 1', 'line 2', 'line 3' })
-- Remove a line:
r:render { 'line 1', '\nline 3' }
assert.are.same(getlines(), { 'line 1', 'line 3' })
end)
end)
it('should handle multiple nested elements', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render {
R.h('text', {}, {
'first line',
}),
'\n',
R.h('text', {}, 'second line'),
}
assert.are.same(getlines(), { 'first line', 'second line' })
r:render {
R.h('text', {}, 'updated first line'),
'\n',
R.h('text', {}, 'third line'),
}
assert.are.same(getlines(), { 'updated first line', 'third line' })
end)
end)
--
-- get_tags_at
--
it('should return no extmarks for an empty buffer', function()
withbuf({}, function()
local r = R.Renderer.new(0)
local pos_infos = r:get_tags_at { 0, 0 }
assert.are.same(pos_infos, {})
end)
end)
it('should return correct extmark for a given position', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render {
R.h('text', { hl = 'HighlightGroup1' }, 'Hello'),
R.h('text', { hl = 'HighlightGroup2' }, ' World'),
}
local pos_infos = r:get_tags_at { 0, 2 }
assert.are.same(#pos_infos, 1)
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup1')
assert.are.same(pos_infos[1].extmark.start, { 0, 0 })
assert.are.same(pos_infos[1].extmark.stop, { 0, 5 })
pos_infos = r:get_tags_at { 0, 4 }
assert.are.same(#pos_infos, 1)
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup1')
assert.are.same(pos_infos[1].extmark.start, { 0, 0 })
assert.are.same(pos_infos[1].extmark.stop, { 0, 5 })
pos_infos = r:get_tags_at { 0, 5 }
assert.are.same(#pos_infos, 1)
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup2')
assert.are.same(pos_infos[1].extmark.start, { 0, 5 })
assert.are.same(pos_infos[1].extmark.stop, { 0, 11 })
-- In insert mode, bounds are eagerly included:
pos_infos = r:get_tags_at({ 0, 5 }, 'i')
assert.are.same(#pos_infos, 2)
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup1')
assert.are.same(pos_infos[2].tag.attributes.hl, 'HighlightGroup2')
end)
end)
it('should return multiple extmarks for overlapping text', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render {
R.h('text', { hl = 'HighlightGroup1' }, {
'Hello',
R.h(
'text',
{ hl = 'HighlightGroup2', extmark = { hl_group = 'HighlightGroup2' } },
' World'
),
}),
}
local pos_infos = r:get_tags_at { 0, 5 }
assert.are.same(#pos_infos, 2)
assert.are.same(pos_infos[1].tag.attributes.hl, 'HighlightGroup2')
assert.are.same(pos_infos[2].tag.attributes.hl, 'HighlightGroup1')
end)
end)
it('repeated patch_lines calls should not change the buffer content', function()
local lines = {
[[{ {]],
[[ bounds = {]],
[[ start1 = { 1, 1 },]],
[[ stop1 = { 4, 1 }]],
[[ },]],
[[ end_right_gravity = true,]],
[[ id = 1,]],
[[ ns_id = 623,]],
[[ ns_name = "my.renderer:91",]],
[[ right_gravity = false]],
[[ } }]],
[[]],
}
withbuf(lines, function()
local Buffer = require 'u.buffer'
R.Renderer.patch_lines(0, nil, lines)
assert.are.same(Buffer.current():all():lines(), lines)
R.Renderer.patch_lines(0, lines, lines)
assert.are.same(Buffer.current():all():lines(), lines)
R.Renderer.patch_lines(0, lines, lines)
assert.are.same(Buffer.current():all():lines(), lines)
end)
end)
it('should fire text-changed events', function()
withbuf({}, function()
local Buffer = require 'u.buffer'
local buf = Buffer.current()
local r = R.Renderer.new(0)
local captured_changed_text = ''
r:render {
R.h('text', {
on_change = function(txt) captured_changed_text = txt end,
}, {
'one\n',
'two\n',
'three\n',
}),
}
vim.fn.setreg('"', 'bleh')
vim.cmd [[normal! ggVGp]]
-- For some reason, the autocmd does not fire in the busted environment.
-- We'll call the handler ourselves:
r:_on_text_changed()
assert.are.same(buf:all():text(), 'bleh')
assert.are.same(captured_changed_text, 'bleh')
end)
end)
it('should find tags by position', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render {
'pre',
R.h('text', {
id = 'outer',
}, {
'inner-pre',
R.h('text', {
id = 'inner',
}, {
'inner-text',
}),
'inner-post',
}),
'post',
}
local tags = r:get_tags_at { 0, 11 }
assert.are.same(#tags, 1)
assert.are.same(tags[1].tag.attributes.id, 'outer')
tags = r:get_tags_at { 0, 12 }
assert.are.same(#tags, 2)
assert.are.same(tags[1].tag.attributes.id, 'inner')
assert.are.same(tags[2].tag.attributes.id, 'outer')
end)
end)
it('should find tags by id', function()
withbuf({}, function()
local r = R.Renderer.new(0)
r:render {
R.h('text', {
id = 'outer',
}, {
'inner-pre',
R.h('text', {
id = 'inner',
}, {
'inner-text',
}),
'inner-post',
}),
'post',
}
local bounds = r:get_tag_bounds 'outer'
assert.are.same(bounds, { start = { 0, 0 }, stop = { 0, 29 } })
bounds = r:get_tag_bounds 'inner'
assert.are.same(bounds, { start = { 0, 9 }, stop = { 0, 19 } })
end)
end)
end)