612 lines
17 KiB
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)
|