cp.nvim/spec/test_panel_spec.lua

612 lines
17 KiB
Lua

describe('cp test panel', function()
local cp
local mock_test_module
local mock_problem_module
local mock_execute_module
local mock_cache
local mock_log_messages
local _temp_files
local created_buffers
local created_windows
before_each(function()
mock_log_messages = {}
_temp_files = {}
created_buffers = {}
created_windows = {}
local mock_logger = {
log = function(msg, level)
table.insert(mock_log_messages, { msg = msg, level = level or vim.log.levels.INFO })
end,
set_config = function() end,
}
package.loaded['cp.log'] = mock_logger
mock_cache = {
load = function() end,
get_test_cases = function()
return nil
end,
set_test_cases = function() end,
}
package.loaded['cp.cache'] = mock_cache
mock_test_module = {
load_test_cases = function()
return true
end,
run_all_test_cases = function() end,
get_test_panel_state = function()
return {
test_cases = {
{
index = 1,
input = '1 2',
expected = '3',
actual = '3',
status = 'pass',
ok = true,
code = 0,
time_ms = 42.5,
},
{
index = 2,
input = '4 5',
expected = '9',
actual = '10',
status = 'fail',
ok = false,
code = 0,
time_ms = 15.3,
},
},
current_index = 1,
}
end,
}
package.loaded['cp.test'] = mock_test_module
mock_problem_module = {
create_context = function()
return {
source_file = 'test.cpp',
binary_file = 'build/test.run',
problem_name = 'test',
}
end,
}
package.loaded['cp.problem'] = mock_problem_module
mock_execute_module = {
compile_problem = function()
return true
end,
}
package.loaded['cp.execute'] = mock_execute_module
vim.fn = vim.tbl_extend('force', vim.fn, {
expand = function(expr)
if expr == '%:t:r' then
return 'test'
end
return ''
end,
tempname = function()
return '/tmp/session.vim'
end,
delete = function() end,
bufwinid = function(buf)
return created_windows[buf] or 1000 + buf
end,
has = function()
return 1
end,
})
local _original_nvim_create_buf = vim.api.nvim_create_buf
vim.api.nvim_create_buf = function(listed, scratch)
local buf_id = #created_buffers + 100
created_buffers[buf_id] = { listed = listed, scratch = scratch }
return buf_id
end
vim.api.nvim_get_current_win = function()
return 1
end
vim.api.nvim_set_option_value = function() end
vim.api.nvim_win_set_buf = function(win, buf)
created_windows[buf] = win
end
vim.api.nvim_buf_set_lines = function() end
vim.api.nvim_buf_is_valid = function()
return true
end
vim.api.nvim_win_call = function(_win, fn)
fn()
end
vim.api.nvim_set_current_win = function() end
vim.cmd = {
split = function() end,
vsplit = function() end,
diffthis = function() end,
}
vim.keymap = {
set = function() end,
}
vim.split = function(str, sep, _opts)
local result = {}
for part in string.gmatch(str, '[^' .. sep .. ']+') do
table.insert(result, part)
end
return result
end
cp = require('cp')
cp.setup({
contests = {
atcoder = {
default_language = 'cpp',
cpp = { extension = 'cpp' },
},
},
})
vim.cmd('silent! %bwipeout!')
end)
after_each(function()
package.loaded['cp.log'] = nil
package.loaded['cp.cache'] = nil
package.loaded['cp.test'] = nil
package.loaded['cp.problem'] = nil
package.loaded['cp.execute'] = nil
vim.cmd('silent! %bwipeout!')
end)
describe('panel creation', function()
it('creates test panel buffers', function()
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
assert.is_true(#created_buffers >= 3)
for _buf_id, buf_info in pairs(created_buffers) do
assert.is_false(buf_info.listed)
assert.is_true(buf_info.scratch)
end
end)
it('sets up correct window layout', function()
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
local split_count = 0
vim.cmd.split = function()
split_count = split_count + 1
end
vim.cmd.vsplit = function()
split_count = split_count + 1
end
cp.handle_command({ fargs = { 'test' } })
assert.equals(2, split_count)
assert.is_true(#created_windows >= 3)
end)
it('applies correct buffer settings', function()
local buffer_options = {}
vim.api.nvim_set_option_value = function(opt, val, scope)
if scope.buf then
buffer_options[scope.buf] = buffer_options[scope.buf] or {}
buffer_options[scope.buf][opt] = val
end
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
for _buf_id, opts in pairs(buffer_options) do
if opts.bufhidden then
assert.equals('wipe', opts.bufhidden)
end
if opts.filetype then
assert.equals('cptest', opts.filetype)
end
end
end)
it('sets up keymaps correctly', function()
local keymaps = {}
vim.keymap.set = function(mode, key, _fn, opts)
table.insert(keymaps, { mode = mode, key = key, buffer = opts.buffer })
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
local ctrl_n_found = false
local ctrl_p_found = false
local q_found = false
for _, keymap in ipairs(keymaps) do
if keymap.key == '<c-n>' then
ctrl_n_found = true
end
if keymap.key == '<c-p>' then
ctrl_p_found = true
end
if keymap.key == 'q' then
q_found = true
end
end
assert.is_true(ctrl_n_found)
assert.is_true(ctrl_p_found)
assert.is_true(q_found)
end)
end)
describe('test case display', function()
it('renders test case tabs correctly', function()
local tab_content = {}
vim.api.nvim_buf_set_lines = function(buf, _start, _end_line, _strict, lines)
tab_content[buf] = lines
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
local found_tab_buffer = false
for buf_id, lines in pairs(tab_content) do
if lines and #lines > 0 then
local content = table.concat(lines, '\n')
if content:match('> 1%..*%[ok:true%]') then
found_tab_buffer = true
assert.is_not_nil(content:match('%[time:43ms%]'))
assert.is_not_nil(content:match('Input:'))
break
end
end
end
assert.is_true(found_tab_buffer)
end)
it('displays input correctly', function()
local tab_content = {}
vim.api.nvim_buf_set_lines = function(buf, _start, _end_line, _strict, lines)
tab_content[buf] = lines
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
local found_input = false
for buf_id, lines in pairs(tab_content) do
if lines and #lines > 0 then
local content = table.concat(lines, '\n')
if content:match('Input:') and content:match('1 2') then
found_input = true
break
end
end
end
assert.is_true(found_input)
end)
it('displays expected output correctly', function()
local expected_content = nil
vim.api.nvim_buf_set_lines = function(buf, _start, _end_line, _strict, lines)
if lines and #lines == 1 and lines[1] == '3' then
expected_content = lines[1]
end
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
assert.equals('3', expected_content)
end)
it('displays actual output correctly', function()
local actual_outputs = {}
vim.api.nvim_buf_set_lines = function(buf, _start, _end_line, _strict, lines)
if lines and #lines > 0 then
table.insert(actual_outputs, lines)
end
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
local found_actual = false
for _, lines in ipairs(actual_outputs) do
if lines[1] == '3' then
found_actual = true
break
end
end
assert.is_true(found_actual)
end)
it('shows diff when test fails', function()
mock_test_module.get_test_panel_state = function()
return {
test_cases = {
{
index = 1,
input = '1 2',
expected = '3',
actual = '4',
status = 'fail',
ok = false,
},
},
current_index = 1,
}
end
local diff_enabled = {}
vim.api.nvim_set_option_value = function(opt, val, scope)
if opt == 'diff' and scope.win then
diff_enabled[scope.win] = val
end
end
local diffthis_called = false
vim.cmd.diffthis = function()
diffthis_called = true
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
assert.is_true(diffthis_called)
local diff_windows = 0
for _, enabled in pairs(diff_enabled) do
if enabled then
diff_windows = diff_windows + 1
end
end
assert.is_true(diff_windows >= 2)
end)
end)
describe('navigation', function()
before_each(function()
mock_test_module.get_test_panel_state = function()
return {
test_cases = {
{ index = 1, input = '1', expected = '1', status = 'pass' },
{ index = 2, input = '2', expected = '2', status = 'pass' },
{ index = 3, input = '3', expected = '3', status = 'fail' },
},
current_index = 2,
}
end
end)
it('navigates to next test case', function()
local test_state = mock_test_module.get_test_panel_state()
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
test_state.current_index = test_state.current_index + 1
assert.equals(3, test_state.current_index)
end)
it('navigates to previous test case', function()
local test_state = mock_test_module.get_test_panel_state()
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
test_state.current_index = test_state.current_index - 1
assert.equals(1, test_state.current_index)
end)
it('wraps around at boundaries', function()
local test_state = mock_test_module.get_test_panel_state()
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
test_state.current_index = 3
test_state.current_index = test_state.current_index + 1
if test_state.current_index > #test_state.test_cases then
test_state.current_index = 1
end
assert.equals(1, test_state.current_index)
test_state.current_index = 1
test_state.current_index = test_state.current_index - 1
if test_state.current_index < 1 then
test_state.current_index = #test_state.test_cases
end
assert.equals(3, test_state.current_index)
end)
it('updates display on navigation', function()
local refresh_count = 0
local original_buf_set_lines = vim.api.nvim_buf_set_lines
vim.api.nvim_buf_set_lines = function(...)
refresh_count = refresh_count + 1
return original_buf_set_lines(...)
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
local initial_count = refresh_count
assert.is_true(initial_count > 0)
end)
end)
describe('test execution integration', function()
it('compiles and runs tests automatically', function()
local compile_called = false
local run_tests_called = false
mock_execute_module.compile_problem = function()
compile_called = true
return true
end
mock_test_module.run_all_test_cases = function()
run_tests_called = true
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
assert.is_true(compile_called)
assert.is_true(run_tests_called)
end)
it('handles compilation failures', function()
local run_tests_called = false
mock_execute_module.compile_problem = function()
return false
end
mock_test_module.run_all_test_cases = function()
run_tests_called = true
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
assert.is_false(run_tests_called)
end)
it('shows execution time', function()
local tab_content = {}
vim.api.nvim_buf_set_lines = function(buf, _start, _end_line, _strict, lines)
tab_content[buf] = lines
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
local found_time = false
for buf_id, lines in pairs(tab_content) do
if lines and #lines > 0 then
local content = table.concat(lines, '\n')
if content:match('%[time:%d+ms%]') then
found_time = true
break
end
end
end
assert.is_true(found_time)
end)
end)
describe('session management', function()
it('saves and restores session correctly', function()
local session_saved = false
local session_restored = false
vim.cmd = function(cmd_str)
if cmd_str:match('mksession') then
session_saved = true
elseif cmd_str:match('source.*session') then
session_restored = true
end
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
assert.is_true(session_saved)
cp.handle_command({ fargs = { 'test' } })
assert.is_true(session_restored)
end)
it('handles multiple panels gracefully', function()
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
assert.has_no_errors(function()
cp.handle_command({ fargs = { 'test' } })
cp.handle_command({ fargs = { 'test' } })
cp.handle_command({ fargs = { 'test' } })
end)
end)
it('cleans up resources on close', function()
local delete_called = false
vim.fn.delete = function()
delete_called = true
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
cp.handle_command({ fargs = { 'test' } })
assert.is_true(delete_called)
local closed_logged = false
for _, log in ipairs(mock_log_messages) do
if log.msg:match('test panel closed') then
closed_logged = true
break
end
end
assert.is_true(closed_logged)
end)
end)
describe('error handling', function()
it('requires platform setup', function()
cp.handle_command({ fargs = { 'test' } })
local error_logged = false
for _, log in ipairs(mock_log_messages) do
if log.level == vim.log.levels.ERROR and log.msg:match('No contest configured') then
error_logged = true
break
end
end
assert.is_true(error_logged)
end)
it('handles missing test cases', function()
mock_test_module.load_test_cases = function()
return false
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
local warning_logged = false
for _, log in ipairs(mock_log_messages) do
if log.level == vim.log.levels.WARN and log.msg:match('no test cases found') then
warning_logged = true
break
end
end
assert.is_true(warning_logged)
end)
it('handles missing current problem', function()
vim.fn.expand = function()
return ''
end
cp.handle_command({ fargs = { 'atcoder', 'abc123', 'a' } })
cp.handle_command({ fargs = { 'test' } })
local error_logged = false
for _, log in ipairs(mock_log_messages) do
if log.level == vim.log.levels.ERROR and log.msg:match('no file open') then
error_logged = true
break
end
end
assert.is_true(error_logged)
end)
end)
end)