mirror of
https://github.com/neovim/neovim.git
synced 2024-12-31 17:13:26 -07:00
test: Add screen test facility
- Add screen.lua which implements a remote screen to verify screen state by tests under functional/ui - Add some basic screen/highlight tests
This commit is contained in:
parent
f8c3a14dc3
commit
1192fbd08a
@ -47,12 +47,6 @@ local function request(method, ...)
|
||||
error(rv[2])
|
||||
end
|
||||
end
|
||||
-- Make sure this will only return after all buffered characters have been
|
||||
-- processed
|
||||
if not loop_stopped then
|
||||
-- Except when the loop has been stopped by a notification triggered
|
||||
-- by the initial request, for example.
|
||||
end
|
||||
return rv
|
||||
end
|
||||
|
||||
@ -70,23 +64,30 @@ local function call_and_stop_on_error(...)
|
||||
return result
|
||||
end
|
||||
|
||||
local function run(request_cb, notification_cb, setup_cb)
|
||||
local function run(request_cb, notification_cb, setup_cb, timeout)
|
||||
local on_request, on_notification, on_setup
|
||||
|
||||
local function on_request(method, args)
|
||||
if request_cb then
|
||||
function on_request(method, args)
|
||||
return call_and_stop_on_error(request_cb, method, args)
|
||||
end
|
||||
|
||||
local function on_notification(method, args)
|
||||
call_and_stop_on_error(notification_cb, method, args)
|
||||
end
|
||||
|
||||
local function on_setup()
|
||||
if notification_cb then
|
||||
function on_notification(method, args)
|
||||
call_and_stop_on_error(notification_cb, method, args)
|
||||
end
|
||||
end
|
||||
|
||||
if setup_cb then
|
||||
function on_setup()
|
||||
call_and_stop_on_error(setup_cb)
|
||||
end
|
||||
end
|
||||
|
||||
loop_stopped = false
|
||||
loop_running = true
|
||||
session:run(on_request, on_notification, on_setup)
|
||||
session:run(on_request, on_notification, on_setup, timeout)
|
||||
loop_running = false
|
||||
if last_error then
|
||||
local err = last_error
|
||||
@ -115,15 +116,6 @@ local function nvim_feed(input)
|
||||
end
|
||||
end
|
||||
|
||||
local function nvim_replace_termcodes(input)
|
||||
-- small hack to stop <C-@> from being replaced by the internal
|
||||
-- representation(which is different and won't work for vim_input)
|
||||
local temp_replacement = 'CCCCCCCCC@@@@@@@@@@'
|
||||
input = input:gsub('<[Cc][-]@>', temp_replacement)
|
||||
local rv = request('vim_replace_termcodes', input, false, true, true)
|
||||
return rv:gsub(temp_replacement, '\000')
|
||||
end
|
||||
|
||||
local function dedent(str)
|
||||
-- find minimum common indent across lines
|
||||
local indent = nil
|
||||
@ -148,7 +140,7 @@ end
|
||||
|
||||
local function feed(...)
|
||||
for _, v in ipairs({...}) do
|
||||
nvim_feed(nvim_replace_termcodes(dedent(v)))
|
||||
nvim_feed(dedent(v))
|
||||
end
|
||||
end
|
||||
|
||||
@ -172,8 +164,11 @@ end
|
||||
|
||||
local function insert(...)
|
||||
nvim_feed('i')
|
||||
rawfeed(...)
|
||||
nvim_feed(nvim_replace_termcodes('<ESC>'))
|
||||
for _, v in ipairs({...}) do
|
||||
local escaped = v:gsub('<', '<lt>')
|
||||
rawfeed(escaped)
|
||||
end
|
||||
nvim_feed('<ESC>')
|
||||
end
|
||||
|
||||
local function execute(...)
|
||||
@ -182,8 +177,8 @@ local function execute(...)
|
||||
-- not a search command, prefix with colon
|
||||
nvim_feed(':')
|
||||
end
|
||||
nvim_feed(v)
|
||||
nvim_feed(nvim_replace_termcodes('<CR>'))
|
||||
nvim_feed(v:gsub('<', '<lt>'))
|
||||
nvim_feed('<CR>')
|
||||
end
|
||||
end
|
||||
|
||||
|
184
test/functional/ui/highlight_spec.lua
Normal file
184
test/functional/ui/highlight_spec.lua
Normal file
@ -0,0 +1,184 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local clear, feed, nvim = helpers.clear, helpers.feed, helpers.nvim
|
||||
local execute = helpers.execute
|
||||
|
||||
describe('Default highlight groups', function()
|
||||
-- Test the default attributes for highlight groups shown by the :highlight
|
||||
-- command
|
||||
local screen, hlgroup_colors
|
||||
|
||||
setup(function()
|
||||
hlgroup_colors = {
|
||||
NonText = nvim('name_to_color', 'Blue'),
|
||||
Question = nvim('name_to_color', 'SeaGreen')
|
||||
}
|
||||
end)
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new()
|
||||
screen:attach()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
screen:detach()
|
||||
end)
|
||||
|
||||
it('window status bar', function()
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {reverse = true, bold = true}, -- StatusLine
|
||||
[2] = {reverse = true} -- StatusLineNC
|
||||
})
|
||||
execute('sp', 'vsp', 'vsp')
|
||||
screen:expect([[
|
||||
^ {2:|} {2:|} |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{1:[No Name] }{2:[No Name] [No Name] }|
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{2:[No Name] }|
|
||||
|
|
||||
]])
|
||||
-- navigate to verify that the attributes are properly moved
|
||||
feed('<c-w>j')
|
||||
screen:expect([[
|
||||
{2:|} {2:|} |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{2:[No Name] [No Name] [No Name] }|
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{1:[No Name] }|
|
||||
|
|
||||
]])
|
||||
-- note that when moving to a window with small width nvim will increase
|
||||
-- the width of the new active window at the expense of a inactive window
|
||||
-- (upstream vim has the same behavior)
|
||||
feed('<c-w>k<c-w>l')
|
||||
screen:expect([[
|
||||
{2:|}^ {2:|} |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{2:[No Name] }{1:[No Name] }{2:[No Name] }|
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{2:[No Name] }|
|
||||
|
|
||||
]])
|
||||
feed('<c-w>l')
|
||||
screen:expect([[
|
||||
{2:|} {2:|}^ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{2:[No Name] [No Name] }{1:[No Name] }|
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{2:[No Name] }|
|
||||
|
|
||||
]])
|
||||
feed('<c-w>h<c-w>h')
|
||||
screen:expect([[
|
||||
^ {2:|} {2:|} |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
~ {2:|}~ {2:|}~ |
|
||||
{1:[No Name] }{2:[No Name] [No Name] }|
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{2:[No Name] }|
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('insert mode text', function()
|
||||
feed('i')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
{1:-- INSERT --} |
|
||||
]], {[1] = {bold = true}})
|
||||
end)
|
||||
|
||||
it('end of file markers', function()
|
||||
nvim('command', 'hi Normal guibg=black')
|
||||
screen:expect([[
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
|
|
||||
]], {[1] = {bold = true, foreground = hlgroup_colors.NonText}})
|
||||
end)
|
||||
|
||||
it('"wait return" text', function()
|
||||
feed(':ls<cr>')
|
||||
screen:expect([[
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
:ls |
|
||||
1 %a "[No Name]" line 1 |
|
||||
{1:Press ENTER or type command to continue}^ |
|
||||
]], {[1] = {bold = true, foreground = hlgroup_colors.Question}})
|
||||
feed('<cr>') -- skip the "Press ENTER..." state or tests will hang
|
||||
end)
|
||||
end)
|
40
test/functional/ui/input_spec.lua
Normal file
40
test/functional/ui/input_spec.lua
Normal file
@ -0,0 +1,40 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local clear, execute, nvim = helpers.clear, helpers.execute, helpers.nvim
|
||||
local feed, next_message, eq = helpers.feed, helpers.next_message, helpers.eq
|
||||
|
||||
describe('mappings', function()
|
||||
local cid
|
||||
|
||||
local add_mapping = function(mapping, send)
|
||||
local str = 'mapped '..mapping
|
||||
local cmd = "nnoremap "..mapping.." :call rpcnotify("..cid..", 'mapped', '"
|
||||
..send:gsub('<', '<lt>').."')<cr>"
|
||||
execute(cmd)
|
||||
end
|
||||
|
||||
local check_mapping = function(mapping, expected)
|
||||
feed(mapping)
|
||||
eq({'notification', 'mapped', {expected}}, next_message())
|
||||
end
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
cid = nvim('get_api_info')[1]
|
||||
add_mapping('<s-up>', '<s-up>')
|
||||
add_mapping('<s-up>', '<s-up>')
|
||||
add_mapping('<c-s-up>', '<c-s-up>')
|
||||
add_mapping('<c-s-a-up>', '<c-s-a-up>')
|
||||
end)
|
||||
|
||||
it('ok', function()
|
||||
check_mapping('<s-up>', '<s-up>')
|
||||
check_mapping('<c-s-up>', '<c-s-up>')
|
||||
check_mapping('<s-c-up>', '<c-s-up>')
|
||||
check_mapping('<c-s-a-up>', '<c-s-a-up>')
|
||||
check_mapping('<s-c-a-up>', '<c-s-a-up>')
|
||||
check_mapping('<c-a-s-up>', '<c-s-a-up>')
|
||||
check_mapping('<s-a-c-up>', '<c-s-a-up>')
|
||||
check_mapping('<a-c-s-up>', '<c-s-a-up>')
|
||||
check_mapping('<a-s-c-up>', '<c-s-a-up>')
|
||||
end)
|
||||
end)
|
380
test/functional/ui/screen.lua
Normal file
380
test/functional/ui/screen.lua
Normal file
@ -0,0 +1,380 @@
|
||||
-- This module contains the Screen class, a complete Nvim screen implementation
|
||||
-- designed for functional testing. The goal is to provide a simple and
|
||||
-- intuitive API for verifying screen state after a set of actions.
|
||||
--
|
||||
-- The screen class exposes a single assertion method, "Screen:expect". This
|
||||
-- method takes a string representing the expected screen state and an optional
|
||||
-- set of attribute identifiers for checking highlighted characters(more on
|
||||
-- this later).
|
||||
--
|
||||
-- The string passed to "expect" will be processed according to these rules:
|
||||
--
|
||||
-- - Each line of the string represents and is matched individually against
|
||||
-- a screen row.
|
||||
-- - The entire string is stripped of common indentation
|
||||
-- - Expected screen rows are stripped of the last character. The last
|
||||
-- character should be used to write pipes(|) that make clear where the
|
||||
-- screen ends
|
||||
-- - The last line is stripped, so the string must have (row count + 1)
|
||||
-- lines.
|
||||
--
|
||||
-- Example usage:
|
||||
--
|
||||
-- local screen = Screen.new(25, 10)
|
||||
-- -- attach the screen to the current Nvim instance
|
||||
-- screen:attach()
|
||||
-- --enter insert mode and type some text
|
||||
-- feed('ihello screen')
|
||||
-- -- declare an expectation for the eventual screen state
|
||||
-- screen:expect([[
|
||||
-- hello screen |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- ~ |
|
||||
-- -- INSERT -- |
|
||||
-- ]]) -- <- Last line is stripped
|
||||
--
|
||||
-- Since screen updates are received asynchronously, "expect" is actually
|
||||
-- specifying the eventual screen state. This is how "expect" works: It will
|
||||
-- start the event loop with a timeout of 5 seconds. Each time it receives an
|
||||
-- update the expected state will be checked against the updated state.
|
||||
--
|
||||
-- If the expected state matches the current state, the event loop will be
|
||||
-- stopped and "expect" will return. If the timeout expires, the last match
|
||||
-- error will be reported and the test will fail.
|
||||
--
|
||||
-- If the second argument is passed to "expect", the screen rows will be
|
||||
-- transformed before being matched against the string lines. The
|
||||
-- transformation rule is simple: Each substring "S" composed with characters
|
||||
-- having the exact same set of attributes will be substituted by "{K:S}",
|
||||
-- where K is a key associated the attribute set via the second argument of
|
||||
-- "expect".
|
||||
--
|
||||
-- Too illustrate how this works, let's say that in the above example we wanted
|
||||
-- to assert that the "-- INSERT --" string is highlighted with the bold
|
||||
-- attribute(which normally is), here's how the call to "expect" should look
|
||||
-- like:
|
||||
--
|
||||
-- screen:expect([[
|
||||
-- hello screen \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- ~ \
|
||||
-- {b:-- INSERT --} \
|
||||
-- ]], {b = {bold = true}})
|
||||
--
|
||||
-- In this case "b" is a string associated with the set composed of one
|
||||
-- attribute: bold. Note that since the {b:} markup is not a real part of the
|
||||
-- screen, the delimiter(|) had to be moved right
|
||||
local helpers = require('test.functional.helpers')
|
||||
local request, run, stop = helpers.request, helpers.run, helpers.stop
|
||||
local eq, dedent = helpers.eq, helpers.dedent
|
||||
|
||||
local Screen = {}
|
||||
Screen.__index = Screen
|
||||
|
||||
function Screen.new(width, height)
|
||||
if not width then
|
||||
width = 53
|
||||
end
|
||||
if not height then
|
||||
height = 14
|
||||
end
|
||||
return setmetatable({
|
||||
_default_attr_ids = nil,
|
||||
_width = width,
|
||||
_height = height,
|
||||
_rows = new_cell_grid(width, height),
|
||||
_mode = 'normal',
|
||||
_mouse_enabled = true,
|
||||
_bell = false,
|
||||
_visual_bell = false,
|
||||
_suspended = true,
|
||||
_attrs = {},
|
||||
_cursor = {
|
||||
enabled = true, row = 1, col = 1
|
||||
},
|
||||
_scroll_region = {
|
||||
top = 1, bot = height, left = 1, right = width
|
||||
}
|
||||
}, Screen)
|
||||
end
|
||||
|
||||
function Screen:set_default_attr_ids(attr_ids)
|
||||
self._default_attr_ids = attr_ids
|
||||
end
|
||||
|
||||
function Screen:attach()
|
||||
request('attach_ui', self._width, self._height)
|
||||
self._suspended = false
|
||||
end
|
||||
|
||||
function Screen:detach()
|
||||
request('detach_ui')
|
||||
self._suspended = true
|
||||
end
|
||||
|
||||
function Screen:expect(expected, attr_ids)
|
||||
-- remove the last line and dedent
|
||||
expected = dedent(expected:gsub('\n[ ]+$', ''))
|
||||
local expected_rows = {}
|
||||
for row in expected:gmatch('[^\n]+') do
|
||||
-- the last character should be the screen delimiter
|
||||
row = row:sub(1, #row - 1)
|
||||
table.insert(expected_rows, row)
|
||||
end
|
||||
local ids = attr_ids or self._default_attr_ids
|
||||
self:_wait(function()
|
||||
for i = 1, self._height do
|
||||
local expected_row = expected_rows[i]
|
||||
local actual_row = self:_row_repr(self._rows[i], ids)
|
||||
if expected_row ~= actual_row then
|
||||
return 'Row '..tostring(i)..' didnt match.\nExpected: "'..
|
||||
expected_row..'"\nActual: "'..actual_row..'"'
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Screen:_wait(check, timeout)
|
||||
local err
|
||||
local function notification_cb(method, args)
|
||||
assert(method == 'redraw')
|
||||
self:_redraw(args)
|
||||
err = check()
|
||||
if not err then
|
||||
stop()
|
||||
end
|
||||
return true
|
||||
end
|
||||
run(nil, notification_cb, nil, timeout or 5000)
|
||||
if err then
|
||||
error(err)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_redraw(updates)
|
||||
for _, update in ipairs(updates) do
|
||||
-- print('--')
|
||||
-- print(require('inspect')(update))
|
||||
local method = update[1]
|
||||
for i = 2, #update do
|
||||
local handler = self['_handle_'..method]
|
||||
handler(self, unpack(update[i]))
|
||||
end
|
||||
-- print(self:_current_screen())
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_handle_resize(width, height)
|
||||
self._rows = new_cell_grid(width, height)
|
||||
end
|
||||
|
||||
function Screen:_handle_clear()
|
||||
self:_clear_block(1, self._height, 1, self._width)
|
||||
end
|
||||
|
||||
function Screen:_handle_eol_clear()
|
||||
local row, col = self._cursor.row, self._cursor.col
|
||||
self:_clear_block(row, 1, col, self._width - col)
|
||||
end
|
||||
|
||||
function Screen:_handle_cursor_goto(row, col)
|
||||
self._cursor.row = row + 1
|
||||
self._cursor.col = col + 1
|
||||
end
|
||||
|
||||
function Screen:_handle_cursor_on()
|
||||
self._cursor.enabled = true
|
||||
end
|
||||
|
||||
function Screen:_handle_cursor_off()
|
||||
self._cursor.enabled = false
|
||||
end
|
||||
|
||||
function Screen:_handle_mouse_on()
|
||||
self._mouse_enabled = true
|
||||
end
|
||||
|
||||
function Screen:_handle_mouse_off()
|
||||
self._mouse_enabled = false
|
||||
end
|
||||
|
||||
function Screen:_handle_insert_mode()
|
||||
self._mode = 'insert'
|
||||
end
|
||||
|
||||
function Screen:_handle_normal_mode()
|
||||
self._mode = 'normal'
|
||||
end
|
||||
|
||||
function Screen:_handle_set_scroll_region(top, bot, left, right)
|
||||
self._scroll_region.top = top + 1
|
||||
self._scroll_region.bot = bot + 1
|
||||
self._scroll_region.left = left + 1
|
||||
self._scroll_region.right = right + 1
|
||||
end
|
||||
|
||||
function Screen:_handle_scroll(count)
|
||||
local top = self._scroll_region.top
|
||||
local bot = self._scroll_region.bot
|
||||
local left = self._scroll_region.left
|
||||
local right = self._scroll_region.right
|
||||
local start, stop, step
|
||||
|
||||
if count > 0 then
|
||||
start = top
|
||||
stop = bot - count
|
||||
step = 1
|
||||
else
|
||||
start = bot
|
||||
stop = top - count
|
||||
step = -1
|
||||
end
|
||||
|
||||
-- shift scroll region
|
||||
for i = start, stop, step do
|
||||
local target = self._rows[i]
|
||||
local source = self._rows[i + count]
|
||||
self:_copy_row_section(target, source, left, right)
|
||||
end
|
||||
|
||||
-- clear invalid rows
|
||||
for i = stop + 1, stop + count, step do
|
||||
self:_clear_row_section(i, left, right)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_handle_highlight_set(attrs)
|
||||
self._attrs = attrs
|
||||
end
|
||||
|
||||
function Screen:_handle_put(str)
|
||||
local cell = self._rows[self._cursor.row][self._cursor.col]
|
||||
cell.text = str
|
||||
cell.attrs = self._attrs
|
||||
self._cursor.col = self._cursor.col + 1
|
||||
end
|
||||
|
||||
function Screen:_handle_bell()
|
||||
self._bell = true
|
||||
end
|
||||
|
||||
function Screen:_handle_visual_bell()
|
||||
self._visual_bell = true
|
||||
end
|
||||
|
||||
function Screen:_handle_suspend()
|
||||
self._suspended = true
|
||||
end
|
||||
|
||||
function Screen:_clear_block(top, lines, left, columns)
|
||||
for i = top, top + lines - 1 do
|
||||
self:_clear_row_section(i, left, left + columns - 1)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_clear_row_section(rownum, startcol, stopcol)
|
||||
local row = self._rows[rownum]
|
||||
for i = startcol, stopcol do
|
||||
row[i].text = ' '
|
||||
row[i].attrs = {}
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_copy_row_section(target, source, startcol, stopcol)
|
||||
for i = startcol, stopcol do
|
||||
target[i].text = source[i].text
|
||||
target[i].attrs = source[i].attrs
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:_row_repr(row, attr_ids)
|
||||
local rv = {}
|
||||
local current_attr_id
|
||||
for i = 1, self._width do
|
||||
local attr_id = get_attr_id(attr_ids, row[i].attrs)
|
||||
if current_attr_id and attr_id ~= current_attr_id then
|
||||
-- close current attribute bracket, add it before any whitespace
|
||||
-- up to the current cell
|
||||
-- table.insert(rv, backward_find_meaningful(rv, i), '}')
|
||||
table.insert(rv, '}')
|
||||
current_attr_id = nil
|
||||
end
|
||||
if not current_attr_id and attr_id then
|
||||
-- open a new attribute bracket
|
||||
table.insert(rv, '{' .. attr_id .. ':')
|
||||
current_attr_id = attr_id
|
||||
end
|
||||
if self._rows[self._cursor.row] == row and self._cursor.col == i then
|
||||
table.insert(rv, '^')
|
||||
else
|
||||
table.insert(rv, row[i].text)
|
||||
end
|
||||
end
|
||||
if current_attr_id then
|
||||
table.insert(rv, '}')
|
||||
end
|
||||
-- return the line representation, but remove empty attribute brackets and
|
||||
-- trailing whitespace
|
||||
return table.concat(rv, '')--:gsub('%s+$', '')
|
||||
end
|
||||
|
||||
|
||||
function Screen:_current_screen()
|
||||
-- get a string that represents the current screen state(debugging helper)
|
||||
local rv = {}
|
||||
for i = 1, self._height do
|
||||
table.insert(rv, "'"..self:_row_repr(self._rows[i]).."'")
|
||||
end
|
||||
return table.concat(rv, '\n')
|
||||
end
|
||||
|
||||
function backward_find_meaningful(tbl, from)
|
||||
for i = from or #tbl, 1, -1 do
|
||||
if tbl[i] ~= ' ' then
|
||||
return i + 1
|
||||
end
|
||||
end
|
||||
return from
|
||||
end
|
||||
|
||||
function new_cell_grid(width, height)
|
||||
local rows = {}
|
||||
for i = 1, height do
|
||||
local cols = {}
|
||||
for j = 1, width do
|
||||
table.insert(cols, {text = ' ', attrs = {}})
|
||||
end
|
||||
table.insert(rows, cols)
|
||||
end
|
||||
return rows
|
||||
end
|
||||
|
||||
function get_attr_id(attr_ids, attrs)
|
||||
if not attr_ids then
|
||||
return
|
||||
end
|
||||
for id, a in pairs(attr_ids) do
|
||||
if a.bold == attrs.bold and a.standout == attrs.standout and
|
||||
a.underline == attrs.underline and a.undercurl == attrs.undercurl and
|
||||
a.italic == attrs.italic and a.reverse == attrs.reverse and
|
||||
a.foreground == attrs.foreground and
|
||||
a.background == attrs.background then
|
||||
return id
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return Screen
|
224
test/functional/ui/screen_basic_spec.lua
Normal file
224
test/functional/ui/screen_basic_spec.lua
Normal file
@ -0,0 +1,224 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local clear, feed, execute = helpers.clear, helpers.feed, helpers.execute
|
||||
local insert = helpers.insert
|
||||
|
||||
describe('Screen', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new()
|
||||
screen:attach()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
screen:detach()
|
||||
end)
|
||||
|
||||
describe('window', function()
|
||||
describe('split', function()
|
||||
it('horizontal', function()
|
||||
execute('sp')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
:sp |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('horizontal and resize', function()
|
||||
execute('sp')
|
||||
execute('resize 8')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
:resize 8 |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('horizontal and vertical', function()
|
||||
execute('sp', 'vsp', 'vsp')
|
||||
screen:expect([[
|
||||
^ | | |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
[No Name] [No Name] [No Name] |
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] |
|
||||
|
|
||||
]])
|
||||
insert('hello')
|
||||
screen:expect([[
|
||||
hell^ |hello |hello |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
[No Name] [+] [No Name] [+] [No Name] [+] |
|
||||
hello |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] [+] |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('tabnew', function()
|
||||
it('creates a new buffer', function()
|
||||
execute('sp', 'vsp', 'vsp')
|
||||
insert('hello')
|
||||
screen:expect([[
|
||||
hell^ |hello |hello |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
[No Name] [+] [No Name] [+] [No Name] [+] |
|
||||
hello |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] [+] |
|
||||
|
|
||||
]])
|
||||
execute('tabnew')
|
||||
insert('hello2')
|
||||
feed('h')
|
||||
screen:expect([[
|
||||
4+ [No Name] + [No Name] X|
|
||||
hell^2 |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
|
|
||||
]])
|
||||
execute('tabprevious')
|
||||
screen:expect([[
|
||||
4+ [No Name] + [No Name] X|
|
||||
hell^ |hello |hello |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
~ |~ |~ |
|
||||
[No Name] [+] [No Name] [+] [No Name] [+] |
|
||||
hello |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
[No Name] [+] |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('insert mode', function()
|
||||
it('move to next line with <cr>', function()
|
||||
feed('iline 1<cr>line 2<cr>')
|
||||
screen:expect([[
|
||||
line 1 |
|
||||
line 2 |
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
-- INSERT -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('command mode', function()
|
||||
it('typing commands', function()
|
||||
feed(':ls')
|
||||
screen:expect([[
|
||||
|
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
:ls^ |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('execute command with multi-line output', function()
|
||||
feed(':ls<cr>')
|
||||
screen:expect([[
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
:ls |
|
||||
1 %a "[No Name]" line 1 |
|
||||
Press ENTER or type command to continue^ |
|
||||
]])
|
||||
feed('<cr>') -- skip the "Press ENTER..." state or tests will hang
|
||||
end)
|
||||
end)
|
||||
end)
|
Loading…
Reference in New Issue
Block a user