Some checks failed
NeoVim tests / code-quality (push) Failing after 1m6s
393 lines
12 KiB
Lua
393 lines
12 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')
|
|
|
|
vim.fn.setreg('"', '')
|
|
vim.cmd [[normal! ggdG]]
|
|
-- We'll call the handler ourselves:
|
|
r:_on_text_changed()
|
|
|
|
assert.are.same(buf:all():text(), '')
|
|
assert.are.same(captured_changed_text, '')
|
|
end)
|
|
|
|
withbuf({}, function()
|
|
local Buffer = require 'u.buffer'
|
|
local buf = Buffer.current()
|
|
local r = R.Renderer.new(0)
|
|
--- @type string?
|
|
local captured_changed_text = nil
|
|
r:render {
|
|
'prefix:',
|
|
R.h('text', {
|
|
on_change = function(txt) captured_changed_text = txt end,
|
|
}, {
|
|
'one',
|
|
}),
|
|
'suffix',
|
|
}
|
|
|
|
vim.fn.setreg('"', 'bleh')
|
|
vim.api.nvim_win_set_cursor(0, { 1, 9 })
|
|
vim.cmd [[normal! vhhd]]
|
|
-- 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(), 'prefix:suffix')
|
|
assert.are.same(captured_changed_text, '')
|
|
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)
|
|
|
|
it('should mount and rerender components', function()
|
|
withbuf({}, function()
|
|
--- @type any
|
|
local leaked_state = { app = {}, c1 = {}, c2 = {} }
|
|
|
|
--- @param ctx u.renderer.FnComponentContext<any, { phase: string, count: integer }>
|
|
local function Counter(ctx)
|
|
if ctx.phase == 'mount' then ctx.state = { phase = ctx.phase, count = 1 } end
|
|
local state = assert(ctx.state)
|
|
state.phase = ctx.phase
|
|
leaked_state[ctx.props.id].ctx = ctx
|
|
|
|
return {
|
|
{ 'Value: ', R.h.Number({}, tostring(state.count)) },
|
|
}
|
|
end
|
|
|
|
--- @param ctx u.renderer.FnComponentContext<any, {
|
|
--- toggle1: boolean,
|
|
--- show2: boolean
|
|
--- }>
|
|
function App(ctx)
|
|
if ctx.phase == 'mount' then ctx.state = { toggle1 = false, show2 = true } end
|
|
local state = assert(ctx.state)
|
|
leaked_state.app.ctx = ctx
|
|
|
|
return {
|
|
state.toggle1 and 'Toggle1' or R.h(Counter, { id = 'c1' }, {}),
|
|
'\n',
|
|
|
|
state.show2 and {
|
|
'\n',
|
|
R.h(Counter, { id = 'c2' }, {}),
|
|
},
|
|
}
|
|
end
|
|
|
|
local renderer = R.Renderer.new()
|
|
renderer:mount(R.h(App, {}, {}))
|
|
|
|
local Buffer = require 'u.buffer'
|
|
local buf = Buffer.current()
|
|
assert.are.same(buf:all():lines(), {
|
|
'Value: 1',
|
|
'',
|
|
'Value: 1',
|
|
})
|
|
assert.are.same(leaked_state.c1.ctx.state.phase, 'mount')
|
|
assert.are.same(leaked_state.c2.ctx.state.phase, 'mount')
|
|
leaked_state.app.ctx:update_immediate { toggle1 = true, show2 = true }
|
|
assert.are.same(buf:all():lines(), {
|
|
'Toggle1',
|
|
'',
|
|
'Value: 1',
|
|
})
|
|
assert.are.same(leaked_state.c1.ctx.state.phase, 'unmount')
|
|
assert.are.same(leaked_state.c2.ctx.state.phase, 'update')
|
|
|
|
leaked_state.app.ctx:update_immediate { toggle1 = true, show2 = false }
|
|
assert.are.same(buf:all():lines(), {
|
|
'Toggle1',
|
|
'',
|
|
})
|
|
assert.are.same(leaked_state.c1.ctx.state.phase, 'unmount')
|
|
assert.are.same(leaked_state.c2.ctx.state.phase, 'unmount')
|
|
|
|
leaked_state.app.ctx:update_immediate { toggle1 = false, show2 = true }
|
|
assert.are.same(buf:all():lines(), {
|
|
'Value: 1',
|
|
'',
|
|
'Value: 1',
|
|
})
|
|
assert.are.same(leaked_state.c1.ctx.state.phase, 'mount')
|
|
assert.are.same(leaked_state.c2.ctx.state.phase, 'mount')
|
|
|
|
leaked_state.c1.ctx:update_immediate { count = 2 }
|
|
assert.are.same(buf:all():lines(), {
|
|
'Value: 2',
|
|
'',
|
|
'Value: 1',
|
|
})
|
|
assert.are.same(leaked_state.c1.ctx.state.phase, 'update')
|
|
assert.are.same(leaked_state.c2.ctx.state.phase, 'update')
|
|
|
|
leaked_state.c2.ctx:update_immediate { count = 3 }
|
|
assert.are.same(buf:all():lines(), {
|
|
'Value: 2',
|
|
'',
|
|
'Value: 3',
|
|
})
|
|
assert.are.same(leaked_state.c1.ctx.state.phase, 'update')
|
|
assert.are.same(leaked_state.c2.ctx.state.phase, 'update')
|
|
end)
|
|
end)
|
|
end)
|