no more tests (for now
This commit is contained in:
parent
00a1d57005
commit
27c265141e
16 changed files with 0 additions and 3381 deletions
|
|
@ -1,243 +0,0 @@
|
|||
describe('ansi parser', function()
|
||||
local ansi = require('cp.ui.ansi')
|
||||
|
||||
describe('bytes_to_string', function()
|
||||
it('returns string as-is', function()
|
||||
local input = 'hello world'
|
||||
assert.equals('hello world', ansi.bytes_to_string(input))
|
||||
end)
|
||||
|
||||
it('converts byte array to string', function()
|
||||
local input = { 104, 101, 108, 108, 111 }
|
||||
assert.equals('hello', ansi.bytes_to_string(input))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('parse_ansi_text', function()
|
||||
it('strips ansi codes from simple text', function()
|
||||
local input = 'Hello \027[31mworld\027[0m!'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('Hello world!', table.concat(result.lines, '\n'))
|
||||
end)
|
||||
|
||||
it('handles text without ansi codes', function()
|
||||
local input = 'Plain text'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('Plain text', table.concat(result.lines, '\n'))
|
||||
assert.equals(0, #result.highlights)
|
||||
end)
|
||||
|
||||
it('creates correct highlight for simple colored text', function()
|
||||
local input = 'Hello \027[31mworld\027[0m!'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals(1, #result.highlights)
|
||||
local highlight = result.highlights[1]
|
||||
assert.equals(0, highlight.line)
|
||||
assert.equals(6, highlight.col_start)
|
||||
assert.equals(11, highlight.col_end)
|
||||
assert.equals('CpAnsiRed', highlight.highlight_group)
|
||||
end)
|
||||
|
||||
it('handles bold text', function()
|
||||
local input = 'Hello \027[1mbold\027[0m world'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('Hello bold world', table.concat(result.lines, '\n'))
|
||||
assert.equals(1, #result.highlights)
|
||||
local highlight = result.highlights[1]
|
||||
assert.equals('CpAnsiBold', highlight.highlight_group)
|
||||
end)
|
||||
|
||||
it('handles italic text', function()
|
||||
local input = 'Hello \027[3mitalic\027[0m world'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('Hello italic world', table.concat(result.lines, '\n'))
|
||||
assert.equals(1, #result.highlights)
|
||||
local highlight = result.highlights[1]
|
||||
assert.equals('CpAnsiItalic', highlight.highlight_group)
|
||||
end)
|
||||
|
||||
it('handles bold + color combination', function()
|
||||
local input = 'Hello \027[1;31mbold red\027[0m world'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('Hello bold red world', table.concat(result.lines, '\n'))
|
||||
assert.equals(1, #result.highlights)
|
||||
local highlight = result.highlights[1]
|
||||
assert.equals('CpAnsiBoldRed', highlight.highlight_group)
|
||||
assert.equals(6, highlight.col_start)
|
||||
assert.equals(14, highlight.col_end)
|
||||
end)
|
||||
|
||||
it('handles italic + color combination', function()
|
||||
local input = 'Hello \027[3;32mitalic green\027[0m world'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('Hello italic green world', table.concat(result.lines, '\n'))
|
||||
assert.equals(1, #result.highlights)
|
||||
local highlight = result.highlights[1]
|
||||
assert.equals('CpAnsiItalicGreen', highlight.highlight_group)
|
||||
end)
|
||||
|
||||
it('handles bold + italic + color combination', function()
|
||||
local input = 'Hello \027[1;3;33mbold italic yellow\027[0m world'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('Hello bold italic yellow world', table.concat(result.lines, '\n'))
|
||||
assert.equals(1, #result.highlights)
|
||||
local highlight = result.highlights[1]
|
||||
assert.equals('CpAnsiBoldItalicYellow', highlight.highlight_group)
|
||||
end)
|
||||
|
||||
it('handles sequential attribute setting', function()
|
||||
local input = 'Hello \027[1m\027[31mbold red\027[0m world'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('Hello bold red world', table.concat(result.lines, '\n'))
|
||||
assert.equals(1, #result.highlights)
|
||||
local highlight = result.highlights[1]
|
||||
assert.equals('CpAnsiBoldRed', highlight.highlight_group)
|
||||
end)
|
||||
|
||||
it('handles selective attribute reset', function()
|
||||
local input = 'Hello \027[1;31mbold red\027[22mno longer bold\027[0m world'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('Hello bold redno longer bold world', table.concat(result.lines, '\n'))
|
||||
assert.equals(2, #result.highlights)
|
||||
|
||||
local bold_red = result.highlights[1]
|
||||
assert.equals('CpAnsiBoldRed', bold_red.highlight_group)
|
||||
assert.equals(6, bold_red.col_start)
|
||||
assert.equals(14, bold_red.col_end)
|
||||
|
||||
local just_red = result.highlights[2]
|
||||
assert.equals('CpAnsiRed', just_red.highlight_group)
|
||||
assert.equals(14, just_red.col_start)
|
||||
assert.equals(28, just_red.col_end)
|
||||
end)
|
||||
|
||||
it('handles bright colors', function()
|
||||
local input = 'Hello \027[91mbright red\027[0m world'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals(1, #result.highlights)
|
||||
local highlight = result.highlights[1]
|
||||
assert.equals('CpAnsiBrightRed', highlight.highlight_group)
|
||||
end)
|
||||
|
||||
it('handles compiler-like output with complex formatting', function()
|
||||
local input =
|
||||
"error.cpp:10:5: \027[1m\027[31merror:\027[0m\027[1m 'undefined' was not declared\027[0m"
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
local clean_text = table.concat(result.lines, '\n')
|
||||
assert.equals("error.cpp:10:5: error: 'undefined' was not declared", clean_text)
|
||||
assert.equals(2, #result.highlights)
|
||||
|
||||
local error_highlight = result.highlights[1]
|
||||
assert.equals('CpAnsiBoldRed', error_highlight.highlight_group)
|
||||
assert.equals(16, error_highlight.col_start)
|
||||
assert.equals(22, error_highlight.col_end)
|
||||
|
||||
local message_highlight = result.highlights[2]
|
||||
assert.equals('CpAnsiBold', message_highlight.highlight_group)
|
||||
assert.equals(22, message_highlight.col_start)
|
||||
assert.equals(51, message_highlight.col_end)
|
||||
end)
|
||||
|
||||
it('handles multiline with persistent state', function()
|
||||
local input = '\027[1;31mline1\nline2\nline3\027[0m'
|
||||
local result = ansi.parse_ansi_text(input)
|
||||
|
||||
assert.equals('line1\nline2\nline3', table.concat(result.lines, '\n'))
|
||||
assert.equals(3, #result.highlights)
|
||||
|
||||
for i, highlight in ipairs(result.highlights) do
|
||||
assert.equals('CpAnsiBoldRed', highlight.highlight_group)
|
||||
assert.equals(i - 1, highlight.line)
|
||||
assert.equals(0, highlight.col_start)
|
||||
assert.equals(5, highlight.col_end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('update_ansi_state', function()
|
||||
it('resets all state on reset code', function()
|
||||
local state = { bold = true, italic = true, foreground = 'Red' }
|
||||
ansi.update_ansi_state(state, '0')
|
||||
|
||||
assert.is_false(state.bold)
|
||||
assert.is_false(state.italic)
|
||||
assert.is_nil(state.foreground)
|
||||
end)
|
||||
|
||||
it('sets individual attributes', function()
|
||||
local state = { bold = false, italic = false, foreground = nil }
|
||||
|
||||
ansi.update_ansi_state(state, '1')
|
||||
assert.is_true(state.bold)
|
||||
|
||||
ansi.update_ansi_state(state, '3')
|
||||
assert.is_true(state.italic)
|
||||
|
||||
ansi.update_ansi_state(state, '31')
|
||||
assert.equals('Red', state.foreground)
|
||||
end)
|
||||
|
||||
it('handles compound codes', function()
|
||||
local state = { bold = false, italic = false, foreground = nil }
|
||||
ansi.update_ansi_state(state, '1;3;31')
|
||||
|
||||
assert.is_true(state.bold)
|
||||
assert.is_true(state.italic)
|
||||
assert.equals('Red', state.foreground)
|
||||
end)
|
||||
|
||||
it('handles selective resets', function()
|
||||
local state = { bold = true, italic = true, foreground = 'Red' }
|
||||
|
||||
ansi.update_ansi_state(state, '22')
|
||||
assert.is_false(state.bold)
|
||||
assert.is_true(state.italic)
|
||||
assert.equals('Red', state.foreground)
|
||||
|
||||
ansi.update_ansi_state(state, '39')
|
||||
assert.is_false(state.bold)
|
||||
assert.is_true(state.italic)
|
||||
assert.is_nil(state.foreground)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('setup_highlight_groups', function()
|
||||
it('creates highlight groups with fallback colors when terminal colors are nil', function()
|
||||
local original_colors = {}
|
||||
for i = 0, 15 do
|
||||
original_colors[i] = vim.g['terminal_color_' .. i]
|
||||
vim.g['terminal_color_' .. i] = nil
|
||||
end
|
||||
|
||||
ansi.setup_highlight_groups()
|
||||
|
||||
local highlight = vim.api.nvim_get_hl(0, { name = 'CpAnsiRed' })
|
||||
assert.is_nil(highlight.fg)
|
||||
|
||||
for i = 0, 15 do
|
||||
vim.g['terminal_color_' .. i] = original_colors[i]
|
||||
end
|
||||
end)
|
||||
|
||||
it('creates highlight groups with proper colors when terminal colors are set', function()
|
||||
vim.g.terminal_color_1 = '#ff0000'
|
||||
|
||||
ansi.setup_highlight_groups()
|
||||
|
||||
local highlight = vim.api.nvim_get_hl(0, { name = 'CpAnsiRed' })
|
||||
assert.equals(0xff0000, highlight.fg)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
describe('cp.cache', function()
|
||||
local cache
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
|
||||
local mock_file_content = '{}'
|
||||
vim.fn.filereadable = function()
|
||||
return 1
|
||||
end
|
||||
vim.fn.readfile = function()
|
||||
return { mock_file_content }
|
||||
end
|
||||
vim.fn.writefile = function(lines)
|
||||
mock_file_content = table.concat(lines, '\n')
|
||||
end
|
||||
vim.fn.mkdir = function() end
|
||||
|
||||
cache = require('cp.cache')
|
||||
cache.load()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
cache.clear_contest_data('atcoder', 'test_contest')
|
||||
cache.clear_contest_data('codeforces', 'test_contest')
|
||||
cache.clear_contest_data('cses', 'test_contest')
|
||||
end)
|
||||
|
||||
describe('load and save', function()
|
||||
it('loads without error when cache file exists', function()
|
||||
assert.has_no_errors(function()
|
||||
cache.load()
|
||||
end)
|
||||
end)
|
||||
|
||||
it('saves and persists data', function()
|
||||
local problems = { { id = 'A', name = 'Problem A' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cache.set_contest_data('atcoder', 'test_contest', problems)
|
||||
end)
|
||||
|
||||
local result = cache.get_contest_data('atcoder', 'test_contest')
|
||||
assert.is_not_nil(result)
|
||||
assert.equals('A', result.problems[1].id)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('contest data', function()
|
||||
it('stores and retrieves contest data', function()
|
||||
local problems = {
|
||||
{ id = 'A', name = 'First Problem' },
|
||||
{ id = 'B', name = 'Second Problem' },
|
||||
}
|
||||
|
||||
cache.set_contest_data('codeforces', 'test_contest', problems)
|
||||
local result = cache.get_contest_data('codeforces', 'test_contest')
|
||||
|
||||
assert.is_not_nil(result)
|
||||
assert.equals(2, #result.problems)
|
||||
assert.equals('A', result.problems[1].id)
|
||||
assert.equals('Second Problem', result.problems[2].name)
|
||||
end)
|
||||
|
||||
it('returns nil for missing contest', function()
|
||||
local result = cache.get_contest_data('atcoder', 'nonexistent_contest')
|
||||
assert.is_nil(result)
|
||||
end)
|
||||
|
||||
it('clears contest data', function()
|
||||
local problems = { { id = 'A' } }
|
||||
cache.set_contest_data('atcoder', 'test_contest', problems)
|
||||
|
||||
cache.clear_contest_data('atcoder', 'test_contest')
|
||||
local result = cache.get_contest_data('atcoder', 'test_contest')
|
||||
|
||||
assert.is_nil(result)
|
||||
end)
|
||||
|
||||
it('handles cses expiry correctly', function()
|
||||
local problems = { { id = 'A' } }
|
||||
cache.set_contest_data('cses', 'test_contest', problems)
|
||||
|
||||
local result = cache.get_contest_data('cses', 'test_contest')
|
||||
assert.is_not_nil(result)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('test cases', function()
|
||||
it('stores and retrieves test cases', function()
|
||||
local test_cases = {
|
||||
{ index = 1, input = '1 2', expected = '3' },
|
||||
{ index = 2, input = '4 5', expected = '9' },
|
||||
}
|
||||
|
||||
cache.set_test_cases('atcoder', 'test_contest', 'A', test_cases)
|
||||
local result = cache.get_test_cases('atcoder', 'test_contest', 'A')
|
||||
|
||||
assert.is_not_nil(result)
|
||||
assert.equals(2, #result)
|
||||
assert.equals('1 2', result[1].input)
|
||||
assert.equals('9', result[2].expected)
|
||||
end)
|
||||
|
||||
it('handles contest-level test cases', function()
|
||||
local test_cases = { { input = 'test', expected = 'output' } }
|
||||
|
||||
cache.set_test_cases('cses', 'test_contest', nil, test_cases)
|
||||
local result = cache.get_test_cases('cses', 'test_contest', nil)
|
||||
|
||||
assert.is_not_nil(result)
|
||||
assert.equals(1, #result)
|
||||
assert.equals('test', result[1].input)
|
||||
end)
|
||||
|
||||
it('returns nil for missing test cases', function()
|
||||
local result = cache.get_test_cases('atcoder', 'nonexistent', 'A')
|
||||
assert.is_nil(result)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('file state', function()
|
||||
it('stores and retrieves file state', function()
|
||||
local file_path = '/tmp/test.cpp'
|
||||
|
||||
cache.set_file_state(file_path, 'atcoder', 'abc123', 'a', 'cpp')
|
||||
local result = cache.get_file_state(file_path)
|
||||
|
||||
assert.is_not_nil(result)
|
||||
assert.equals('atcoder', result.platform)
|
||||
assert.equals('abc123', result.contest_id)
|
||||
assert.equals('a', result.problem_id)
|
||||
assert.equals('cpp', result.language)
|
||||
end)
|
||||
|
||||
it('handles cses file state without problem_id', function()
|
||||
local file_path = '/tmp/cses.py'
|
||||
|
||||
cache.set_file_state(file_path, 'cses', '1068', nil, 'python')
|
||||
local result = cache.get_file_state(file_path)
|
||||
|
||||
assert.is_not_nil(result)
|
||||
assert.equals('cses', result.platform)
|
||||
assert.equals('1068', result.contest_id)
|
||||
assert.is_nil(result.problem_id)
|
||||
assert.equals('python', result.language)
|
||||
end)
|
||||
|
||||
it('returns nil for missing file state', function()
|
||||
local result = cache.get_file_state('/nonexistent/file.cpp')
|
||||
assert.is_nil(result)
|
||||
end)
|
||||
|
||||
it('overwrites existing file state', function()
|
||||
local file_path = '/tmp/overwrite.cpp'
|
||||
|
||||
cache.set_file_state(file_path, 'atcoder', 'abc123', 'a', 'cpp')
|
||||
cache.set_file_state(file_path, 'codeforces', '1934', 'b', 'python')
|
||||
|
||||
local result = cache.get_file_state(file_path)
|
||||
|
||||
assert.is_not_nil(result)
|
||||
assert.equals('codeforces', result.platform)
|
||||
assert.equals('1934', result.contest_id)
|
||||
assert.equals('b', result.problem_id)
|
||||
assert.equals('python', result.language)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('cache management', function()
|
||||
it('clears all cache data', function()
|
||||
cache.set_contest_data('atcoder', 'test_contest', { { id = 'A' } })
|
||||
cache.set_contest_data('codeforces', 'test_contest', { { id = 'B' } })
|
||||
cache.set_file_state('/tmp/test.cpp', 'atcoder', 'abc123', 'a', 'cpp')
|
||||
|
||||
cache.clear_all()
|
||||
|
||||
assert.is_nil(cache.get_contest_data('atcoder', 'test_contest'))
|
||||
assert.is_nil(cache.get_contest_data('codeforces', 'test_contest'))
|
||||
assert.is_nil(cache.get_file_state('/tmp/test.cpp'))
|
||||
end)
|
||||
|
||||
it('clears cache for specific platform', function()
|
||||
cache.set_contest_data('atcoder', 'test_contest', { { id = 'A' } })
|
||||
cache.set_contest_data('codeforces', 'test_contest', { { id = 'B' } })
|
||||
cache.set_contest_list('atcoder', { { id = '123', name = 'Test' } })
|
||||
cache.set_contest_list('codeforces', { { id = '456', name = 'Test' } })
|
||||
|
||||
cache.clear_platform('atcoder')
|
||||
|
||||
assert.is_nil(cache.get_contest_data('atcoder', 'test_contest'))
|
||||
assert.is_nil(cache.get_contest_list('atcoder'))
|
||||
assert.is_not_nil(cache.get_contest_data('codeforces', 'test_contest'))
|
||||
assert.is_not_nil(cache.get_contest_list('codeforces'))
|
||||
end)
|
||||
|
||||
it('handles clear platform for non-existent platform', function()
|
||||
assert.has_no_errors(function()
|
||||
cache.clear_platform('nonexistent')
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,729 +0,0 @@
|
|||
describe('cp command parsing', function()
|
||||
local cp
|
||||
local logged_messages
|
||||
|
||||
before_each(function()
|
||||
logged_messages = {}
|
||||
local mock_logger = {
|
||||
log = function(msg, level)
|
||||
table.insert(logged_messages, { msg = msg, level = level })
|
||||
end,
|
||||
set_config = function() end,
|
||||
}
|
||||
package.loaded['cp.log'] = mock_logger
|
||||
|
||||
local mock_setup = {
|
||||
set_platform = function()
|
||||
return true
|
||||
end,
|
||||
setup_contest = function() end,
|
||||
navigate_problem = function() end,
|
||||
setup_problem = function() end,
|
||||
}
|
||||
package.loaded['cp.setup'] = mock_setup
|
||||
|
||||
local mock_state = {
|
||||
get_platform = function()
|
||||
return 'atcoder'
|
||||
end,
|
||||
get_contest_id = function()
|
||||
return 'abc123'
|
||||
end,
|
||||
get_problem_id = function()
|
||||
return 'a'
|
||||
end,
|
||||
set_platform = function() end,
|
||||
set_contest_id = function() end,
|
||||
set_problem_id = function() end,
|
||||
}
|
||||
package.loaded['cp.state'] = mock_state
|
||||
|
||||
local mock_ui_panel = {
|
||||
toggle_run_panel = function() end,
|
||||
toggle_interactive = function() end,
|
||||
}
|
||||
package.loaded['cp.ui.panel'] = mock_ui_panel
|
||||
|
||||
local mock_cache = {
|
||||
load = function() end,
|
||||
get_contest_data = function()
|
||||
return {
|
||||
problems = {
|
||||
{ id = 'a', name = 'Problem A' },
|
||||
{ id = 'b', name = 'Problem B' },
|
||||
},
|
||||
}
|
||||
end,
|
||||
}
|
||||
package.loaded['cp.cache'] = mock_cache
|
||||
|
||||
local mock_restore = {
|
||||
restore_from_current_file = function()
|
||||
logged_messages[#logged_messages + 1] =
|
||||
{ msg = 'No file is currently open', level = vim.log.levels.ERROR }
|
||||
end,
|
||||
}
|
||||
package.loaded['cp.restore'] = mock_restore
|
||||
|
||||
local mock_picker = {
|
||||
handle_pick_action = function() end,
|
||||
}
|
||||
package.loaded['cp.commands.picker'] = mock_picker
|
||||
|
||||
local mock_cache_commands = {
|
||||
handle_cache_command = function(cmd)
|
||||
if cmd.subcommand == 'clear' then
|
||||
if cmd.platform then
|
||||
local constants = require('cp.constants')
|
||||
if vim.tbl_contains(constants.PLATFORMS, cmd.platform) then
|
||||
logged_messages[#logged_messages + 1] = { msg = 'cleared cache for ' .. cmd.platform }
|
||||
else
|
||||
logged_messages[#logged_messages + 1] =
|
||||
{ msg = 'unknown platform: ' .. cmd.platform, level = vim.log.levels.ERROR }
|
||||
end
|
||||
else
|
||||
logged_messages[#logged_messages + 1] = { msg = 'cleared all cache' }
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
package.loaded['cp.commands.cache'] = mock_cache_commands
|
||||
|
||||
cp = require('cp')
|
||||
cp.setup({
|
||||
contests = {
|
||||
atcoder = {
|
||||
default_language = 'cpp',
|
||||
cpp = { extension = 'cpp' },
|
||||
},
|
||||
cses = {
|
||||
default_language = 'cpp',
|
||||
cpp = { extension = 'cpp' },
|
||||
},
|
||||
},
|
||||
})
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
package.loaded['cp.log'] = nil
|
||||
package.loaded['cp.setup'] = nil
|
||||
package.loaded['cp.state'] = nil
|
||||
package.loaded['cp.ui.panel'] = nil
|
||||
package.loaded['cp.cache'] = nil
|
||||
package.loaded['cp.restore'] = nil
|
||||
package.loaded['cp.commands.picker'] = nil
|
||||
package.loaded['cp.commands.cache'] = nil
|
||||
package.loaded['cp'] = nil
|
||||
package.loaded['cp.commands.init'] = nil
|
||||
end)
|
||||
|
||||
describe('empty arguments', function()
|
||||
it('attempts file state restoration for no arguments', function()
|
||||
local opts = { fargs = {} }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
assert.is_true(#logged_messages > 0)
|
||||
local error_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if
|
||||
log_entry.level == vim.log.levels.ERROR
|
||||
and log_entry.msg:match('No file is currently open')
|
||||
then
|
||||
error_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(error_logged)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('action commands', function()
|
||||
it('handles test action without error', function()
|
||||
local opts = { fargs = { 'run' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles next action without error', function()
|
||||
local opts = { fargs = { 'next' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles prev action without error', function()
|
||||
local opts = { fargs = { 'prev' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('platform commands', function()
|
||||
it('handles platform-only command', function()
|
||||
local opts = { fargs = { 'atcoder' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles contest setup command', function()
|
||||
local opts = { fargs = { 'atcoder', 'abc123' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles cses problem command', function()
|
||||
local opts = { fargs = { 'cses', 'sorting_and_searching', '1234' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles full setup command', function()
|
||||
local opts = { fargs = { 'atcoder', 'abc123', 'a' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('logs error for too many arguments', function()
|
||||
local opts = { fargs = { 'atcoder', 'abc123', 'a', 'b', 'extra' } }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
local error_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.level == vim.log.levels.ERROR then
|
||||
error_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(error_logged)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('language flag parsing', function()
|
||||
it('logs error for --lang flag missing value', function()
|
||||
local opts = { fargs = { 'run', '--lang' } }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
local error_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if
|
||||
log_entry.level == vim.log.levels.ERROR and log_entry.msg:match('--lang requires a value')
|
||||
then
|
||||
error_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(error_logged)
|
||||
end)
|
||||
|
||||
it('handles language with equals format', function()
|
||||
local opts = { fargs = { 'atcoder', '--lang=python' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles language with space format', function()
|
||||
local opts = { fargs = { 'atcoder', '--lang', 'cpp' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles contest with language flag', function()
|
||||
local opts = { fargs = { 'atcoder', 'abc123', '--lang=python' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('debug flag parsing', function()
|
||||
it('handles debug flag without error', function()
|
||||
local opts = { fargs = { 'run', '--debug' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles combined language and debug flags', function()
|
||||
local opts = { fargs = { 'run', '--lang=cpp', '--debug' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('restore from file', function()
|
||||
it('returns restore_from_file type for empty args', function()
|
||||
local opts = { fargs = {} }
|
||||
local logged_error = false
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
for _, log in ipairs(logged_messages) do
|
||||
if log.level == vim.log.levels.ERROR and log.msg:match('No file is currently open') then
|
||||
logged_error = true
|
||||
end
|
||||
end
|
||||
|
||||
assert.is_true(logged_error)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('invalid commands', function()
|
||||
it('logs error for invalid platform', function()
|
||||
local opts = { fargs = { 'invalid_platform' } }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
local error_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.level == vim.log.levels.ERROR then
|
||||
error_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(error_logged)
|
||||
end)
|
||||
|
||||
it('logs error for invalid action', function()
|
||||
local opts = { fargs = { 'invalid_action' } }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
local error_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.level == vim.log.levels.ERROR then
|
||||
error_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(error_logged)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('edge cases', function()
|
||||
it('handles empty string arguments', function()
|
||||
local opts = { fargs = { '' } }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
local error_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.level == vim.log.levels.ERROR then
|
||||
error_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(error_logged)
|
||||
end)
|
||||
|
||||
it('handles flag order variations', function()
|
||||
local opts = { fargs = { '--debug', 'run', '--lang=python' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles multiple language flags', function()
|
||||
local opts = { fargs = { 'run', '--lang=cpp', '--lang=python' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('command validation', function()
|
||||
it('validates platform names against constants', function()
|
||||
local constants = require('cp.constants')
|
||||
|
||||
for _, platform in ipairs(constants.PLATFORMS) do
|
||||
local opts = { fargs = { platform } }
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
it('validates action names against constants', function()
|
||||
local constants = require('cp.constants')
|
||||
|
||||
for _, action in ipairs(constants.ACTIONS) do
|
||||
local opts = { fargs = { action } }
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('cache commands', function()
|
||||
it('handles cache clear without platform', function()
|
||||
local opts = { fargs = { 'cache', 'clear' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
|
||||
local success_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.msg and log_entry.msg:match('cleared all cache') then
|
||||
success_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(success_logged)
|
||||
end)
|
||||
|
||||
it('handles cache clear with valid platform', function()
|
||||
local opts = { fargs = { 'cache', 'clear', 'atcoder' } }
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command(opts)
|
||||
end)
|
||||
|
||||
local success_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.msg and log_entry.msg:match('cleared cache for atcoder') then
|
||||
success_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(success_logged)
|
||||
end)
|
||||
|
||||
it('logs error for cache clear with invalid platform', function()
|
||||
local opts = { fargs = { 'cache', 'clear', 'invalid_platform' } }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
local error_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.level == vim.log.levels.ERROR and log_entry.msg:match('unknown platform') then
|
||||
error_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(error_logged)
|
||||
end)
|
||||
|
||||
it('logs error for cache command without subcommand', function()
|
||||
local opts = { fargs = { 'cache' } }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
local error_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if
|
||||
log_entry.level == vim.log.levels.ERROR
|
||||
and log_entry.msg:match('cache command requires subcommand')
|
||||
then
|
||||
error_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(error_logged)
|
||||
end)
|
||||
|
||||
it('logs error for invalid cache subcommand', function()
|
||||
local opts = { fargs = { 'cache', 'invalid' } }
|
||||
|
||||
cp.handle_command(opts)
|
||||
|
||||
local error_logged = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if
|
||||
log_entry.level == vim.log.levels.ERROR
|
||||
and log_entry.msg:match('unknown cache subcommand')
|
||||
then
|
||||
error_logged = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(error_logged)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('CP command completion', function()
|
||||
local complete_fn
|
||||
|
||||
before_each(function()
|
||||
package.loaded['cp'] = nil
|
||||
package.loaded['cp.cache'] = nil
|
||||
|
||||
complete_fn = function(ArgLead, CmdLine, _)
|
||||
local constants = require('cp.constants')
|
||||
local platforms = constants.PLATFORMS
|
||||
local actions = constants.ACTIONS
|
||||
|
||||
local args = vim.split(vim.trim(CmdLine), '%s+')
|
||||
local num_args = #args
|
||||
if CmdLine:sub(-1) == ' ' then
|
||||
num_args = num_args + 1
|
||||
end
|
||||
|
||||
if num_args == 2 then
|
||||
local candidates = {}
|
||||
local state = require('cp.state')
|
||||
if state.get_platform() and state.get_contest_id() then
|
||||
vim.list_extend(candidates, actions)
|
||||
local cache = require('cp.cache')
|
||||
cache.load()
|
||||
local contest_data =
|
||||
cache.get_contest_data(state.get_platform(), state.get_contest_id())
|
||||
if contest_data and contest_data.problems then
|
||||
for _, problem in ipairs(contest_data.problems) do
|
||||
table.insert(candidates, problem.id)
|
||||
end
|
||||
end
|
||||
else
|
||||
vim.list_extend(candidates, platforms)
|
||||
table.insert(candidates, 'cache')
|
||||
table.insert(candidates, 'pick')
|
||||
end
|
||||
return vim.tbl_filter(function(cmd)
|
||||
return cmd:find(ArgLead, 1, true) == 1
|
||||
end, candidates)
|
||||
elseif num_args == 3 then
|
||||
if args[2] == 'cache' then
|
||||
return vim.tbl_filter(function(cmd)
|
||||
return cmd:find(ArgLead, 1, true) == 1
|
||||
end, { 'clear' })
|
||||
end
|
||||
elseif num_args == 4 then
|
||||
if args[2] == 'cache' and args[3] == 'clear' then
|
||||
return vim.tbl_filter(function(cmd)
|
||||
return cmd:find(ArgLead, 1, true) == 1
|
||||
end, platforms)
|
||||
elseif vim.tbl_contains(platforms, args[2]) then
|
||||
local cache = require('cp.cache')
|
||||
cache.load()
|
||||
local contest_data = cache.get_contest_data(args[2], args[3])
|
||||
if contest_data and contest_data.problems then
|
||||
local candidates = {}
|
||||
for _, problem in ipairs(contest_data.problems) do
|
||||
table.insert(candidates, problem.id)
|
||||
end
|
||||
return vim.tbl_filter(function(cmd)
|
||||
return cmd:find(ArgLead, 1, true) == 1
|
||||
end, candidates)
|
||||
end
|
||||
end
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
package.loaded['cp.state'] = {
|
||||
get_platform = function()
|
||||
return nil
|
||||
end,
|
||||
get_contest_id = function()
|
||||
return nil
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded['cp.cache'] = {
|
||||
load = function() end,
|
||||
get_contest_data = function()
|
||||
return nil
|
||||
end,
|
||||
}
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
package.loaded['cp'] = nil
|
||||
package.loaded['cp.cache'] = nil
|
||||
end)
|
||||
|
||||
it('completes platforms and global actions without contest configuration', function()
|
||||
local result = complete_fn('', 'CP ', 3)
|
||||
|
||||
assert.is_table(result)
|
||||
|
||||
local has_atcoder = false
|
||||
local has_codeforces = false
|
||||
local has_cses = false
|
||||
local has_cache = false
|
||||
local has_pick = false
|
||||
local has_run = false
|
||||
local has_next = false
|
||||
local has_prev = false
|
||||
|
||||
for _, item in ipairs(result) do
|
||||
if item == 'atcoder' then
|
||||
has_atcoder = true
|
||||
end
|
||||
if item == 'codeforces' then
|
||||
has_codeforces = true
|
||||
end
|
||||
if item == 'cses' then
|
||||
has_cses = true
|
||||
end
|
||||
if item == 'cache' then
|
||||
has_cache = true
|
||||
end
|
||||
if item == 'pick' then
|
||||
has_pick = true
|
||||
end
|
||||
if item == 'run' then
|
||||
has_run = true
|
||||
end
|
||||
if item == 'next' then
|
||||
has_next = true
|
||||
end
|
||||
if item == 'prev' then
|
||||
has_prev = true
|
||||
end
|
||||
end
|
||||
|
||||
assert.is_true(has_atcoder)
|
||||
assert.is_true(has_codeforces)
|
||||
assert.is_true(has_cses)
|
||||
assert.is_true(has_cache)
|
||||
assert.is_true(has_pick)
|
||||
assert.is_false(has_run)
|
||||
assert.is_false(has_next)
|
||||
assert.is_false(has_prev)
|
||||
end)
|
||||
|
||||
it('completes all actions and problems when contest context exists', function()
|
||||
package.loaded['cp.state'] = {
|
||||
get_platform = function()
|
||||
return 'atcoder'
|
||||
end,
|
||||
get_contest_id = function()
|
||||
return 'abc350'
|
||||
end,
|
||||
}
|
||||
package.loaded['cp.cache'] = {
|
||||
load = function() end,
|
||||
get_contest_data = function()
|
||||
return {
|
||||
problems = {
|
||||
{ id = 'a' },
|
||||
{ id = 'b' },
|
||||
{ id = 'c' },
|
||||
},
|
||||
}
|
||||
end,
|
||||
}
|
||||
|
||||
local result = complete_fn('', 'CP ', 3)
|
||||
|
||||
assert.is_table(result)
|
||||
|
||||
local items = {}
|
||||
for _, item in ipairs(result) do
|
||||
items[item] = true
|
||||
end
|
||||
|
||||
assert.is_true(items['run'])
|
||||
assert.is_true(items['next'])
|
||||
assert.is_true(items['prev'])
|
||||
assert.is_true(items['pick'])
|
||||
assert.is_true(items['cache'])
|
||||
|
||||
assert.is_true(items['a'])
|
||||
assert.is_true(items['b'])
|
||||
assert.is_true(items['c'])
|
||||
end)
|
||||
|
||||
it('completes cache subcommands', function()
|
||||
local result = complete_fn('c', 'CP cache c', 10)
|
||||
|
||||
assert.is_table(result)
|
||||
assert.equals(1, #result)
|
||||
assert.equals('clear', result[1])
|
||||
end)
|
||||
|
||||
it('completes cache subcommands with exact match', function()
|
||||
local result = complete_fn('clear', 'CP cache clear', 14)
|
||||
|
||||
assert.is_table(result)
|
||||
assert.equals(1, #result)
|
||||
assert.equals('clear', result[1])
|
||||
end)
|
||||
|
||||
it('completes platforms for cache clear', function()
|
||||
local result = complete_fn('a', 'CP cache clear a', 16)
|
||||
|
||||
assert.is_table(result)
|
||||
|
||||
local has_atcoder = false
|
||||
local has_cache = false
|
||||
|
||||
for _, item in ipairs(result) do
|
||||
if item == 'atcoder' then
|
||||
has_atcoder = true
|
||||
end
|
||||
if item == 'cache' then
|
||||
has_cache = true
|
||||
end
|
||||
end
|
||||
|
||||
assert.is_true(has_atcoder)
|
||||
assert.is_false(has_cache)
|
||||
end)
|
||||
|
||||
it('filters completions based on current input', function()
|
||||
local result = complete_fn('at', 'CP at', 5)
|
||||
|
||||
assert.is_table(result)
|
||||
assert.equals(1, #result)
|
||||
assert.equals('atcoder', result[1])
|
||||
end)
|
||||
|
||||
it('returns empty array when no matches', function()
|
||||
local result = complete_fn('xyz', 'CP xyz', 6)
|
||||
|
||||
assert.is_table(result)
|
||||
assert.equals(0, #result)
|
||||
end)
|
||||
|
||||
it('handles problem completion for platform contest', function()
|
||||
package.loaded['cp.cache'] = {
|
||||
load = function() end,
|
||||
get_contest_data = function(platform, contest)
|
||||
if platform == 'atcoder' and contest == 'abc350' then
|
||||
return {
|
||||
problems = {
|
||||
{ id = 'a' },
|
||||
{ id = 'b' },
|
||||
},
|
||||
}
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
}
|
||||
|
||||
local result = complete_fn('a', 'CP atcoder abc350 a', 18)
|
||||
|
||||
assert.is_table(result)
|
||||
assert.equals(1, #result)
|
||||
assert.equals('a', result[1])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,334 +0,0 @@
|
|||
describe('cp.config', function()
|
||||
local config
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
config = require('cp.config')
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
end)
|
||||
|
||||
describe('setup', function()
|
||||
it('returns defaults with nil input', function()
|
||||
local result = config.setup()
|
||||
|
||||
assert.equals('table', type(result.contests))
|
||||
assert.equals('table', type(result.snippets))
|
||||
assert.equals('table', type(result.hooks))
|
||||
assert.equals('table', type(result.scrapers))
|
||||
assert.is_false(result.debug)
|
||||
assert.is_nil(result.filename)
|
||||
end)
|
||||
|
||||
it('merges user config with defaults', function()
|
||||
local user_config = {
|
||||
debug = true,
|
||||
contests = { test_contest = { cpp = { extension = 'cpp' } } },
|
||||
}
|
||||
|
||||
local result = config.setup(user_config)
|
||||
|
||||
assert.is_true(result.debug)
|
||||
assert.equals('table', type(result.contests.test_contest))
|
||||
assert.equals('table', type(result.scrapers))
|
||||
end)
|
||||
|
||||
it('allows custom extensions', function()
|
||||
local custom_config = {
|
||||
contests = {
|
||||
test_contest = {
|
||||
cpp = { extension = 'custom' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.has_no.errors(function()
|
||||
config.setup(custom_config)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('validates scraper platforms', function()
|
||||
local invalid_config = {
|
||||
scrapers = { 'invalid_platform' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('validates scraper values are strings', function()
|
||||
local invalid_config = {
|
||||
scrapers = { 123 },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('validates diff_mode values', function()
|
||||
local valid_config = {
|
||||
run_panel = {
|
||||
diff_mode = 'none',
|
||||
},
|
||||
}
|
||||
|
||||
assert.has_no.errors(function()
|
||||
config.setup(valid_config)
|
||||
end)
|
||||
|
||||
local invalid_config = {
|
||||
run_panel = {
|
||||
diff_mode = 'invalid_mode',
|
||||
},
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('validates hook functions', function()
|
||||
local invalid_config = {
|
||||
hooks = { before_run = 'not_a_function' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('run_panel config validation', function()
|
||||
it('validates ansi is boolean', function()
|
||||
local invalid_config = {
|
||||
run_panel = { ansi = 'invalid' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end, 'ansi: expected ansi color parsing must be enabled xor disabled, got string')
|
||||
end)
|
||||
|
||||
it('validates diff_mode values', function()
|
||||
local invalid_config = {
|
||||
run_panel = { diff_mode = 'invalid' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('validates next_test_key is non-empty string', function()
|
||||
local invalid_config = {
|
||||
run_panel = { next_test_key = '' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('validates prev_test_key is non-empty string', function()
|
||||
local invalid_config = {
|
||||
run_panel = { prev_test_key = '' },
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('accepts valid run_panel config', function()
|
||||
local valid_config = {
|
||||
run_panel = {
|
||||
ansi = false,
|
||||
diff_mode = 'git',
|
||||
next_test_key = 'j',
|
||||
prev_test_key = 'k',
|
||||
},
|
||||
}
|
||||
|
||||
assert.has_no.errors(function()
|
||||
config.setup(valid_config)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('auto-configuration', function()
|
||||
it('sets default extensions for cpp and python', function()
|
||||
local user_config = {
|
||||
contests = {
|
||||
test = {
|
||||
cpp = { compile = { 'g++' } },
|
||||
python = { test = { 'python3' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local result = config.setup(user_config)
|
||||
|
||||
assert.equals('cpp', result.contests.test.cpp.extension)
|
||||
assert.equals('py', result.contests.test.python.extension)
|
||||
end)
|
||||
|
||||
it('sets default_language to cpp when available', function()
|
||||
local user_config = {
|
||||
contests = {
|
||||
test = {
|
||||
cpp = { compile = { 'g++' } },
|
||||
python = { test = { 'python3' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local result = config.setup(user_config)
|
||||
|
||||
assert.equals('cpp', result.contests.test.default_language)
|
||||
end)
|
||||
|
||||
it('sets default_language to single available language when only one configured', function()
|
||||
local user_config = {
|
||||
contests = {
|
||||
test = {
|
||||
python = { test = { 'python3' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local result = config.setup(user_config)
|
||||
|
||||
assert.equals('python', result.contests.test.default_language)
|
||||
end)
|
||||
|
||||
it('sets default_language to single available language even when not cpp', function()
|
||||
local user_config = {
|
||||
contests = {
|
||||
test = {
|
||||
rust = {
|
||||
test = { './target/release/solution' },
|
||||
extension = 'rs',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local result = config.setup(user_config)
|
||||
|
||||
assert.equals('rust', result.contests.test.default_language)
|
||||
end)
|
||||
|
||||
it('uses first available language when multiple configured', function()
|
||||
local user_config = {
|
||||
contests = {
|
||||
test = {
|
||||
python = { test = { 'python3' } },
|
||||
cpp = { compile = { 'g++' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local result = config.setup(user_config)
|
||||
|
||||
assert.is_true(vim.tbl_contains({ 'cpp', 'python' }, result.contests.test.default_language))
|
||||
end)
|
||||
|
||||
it('preserves explicit default_language', function()
|
||||
local user_config = {
|
||||
contests = {
|
||||
test = {
|
||||
cpp = { compile = { 'g++' } },
|
||||
python = { test = { 'python3' } },
|
||||
default_language = 'python',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local result = config.setup(user_config)
|
||||
|
||||
assert.equals('python', result.contests.test.default_language)
|
||||
end)
|
||||
|
||||
it('errors when no language configurations exist', function()
|
||||
local invalid_config = {
|
||||
contests = {
|
||||
test = {},
|
||||
},
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end, 'No language configurations found')
|
||||
end)
|
||||
|
||||
it('allows custom language names', function()
|
||||
local user_config = {
|
||||
contests = {
|
||||
test = {
|
||||
rust = {
|
||||
compile = { 'rustc', '{source}', '-o', '{binary}' },
|
||||
test = { '{binary}' },
|
||||
extension = 'rs',
|
||||
},
|
||||
cpp = { compile = { 'g++' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.has_no.errors(function()
|
||||
local result = config.setup(user_config)
|
||||
assert.equals('cpp', result.contests.test.default_language)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('picker validation', function()
|
||||
it('validates picker is valid value', function()
|
||||
local invalid_config = {
|
||||
picker = 'invalid_picker',
|
||||
}
|
||||
|
||||
assert.has_error(function()
|
||||
config.setup(invalid_config)
|
||||
end, "Invalid picker 'invalid_picker'. Must be 'telescope' or 'fzf-lua'")
|
||||
end)
|
||||
|
||||
it('allows nil picker', function()
|
||||
assert.has_no.errors(function()
|
||||
local result = config.setup({ picker = nil })
|
||||
assert.is_nil(result.picker)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('allows telescope picker without checking availability', function()
|
||||
assert.has_no.errors(function()
|
||||
local result = config.setup({ picker = 'telescope' })
|
||||
assert.equals('telescope', result.picker)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('allows fzf-lua picker without checking availability', function()
|
||||
assert.has_no.errors(function()
|
||||
local result = config.setup({ picker = 'fzf-lua' })
|
||||
assert.equals('fzf-lua', result.picker)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('default_filename', function()
|
||||
it('generates lowercase contest filename', function()
|
||||
local result = config.default_filename('ABC123')
|
||||
assert.equals('abc123', result)
|
||||
end)
|
||||
|
||||
it('combines contest and problem ids', function()
|
||||
local result = config.default_filename('ABC123', 'A')
|
||||
assert.equals('abc123a', result)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
describe('cp.diff', function()
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
local diff
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
diff = require('cp.ui.diff')
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
end)
|
||||
|
||||
describe('get_available_backends', function()
|
||||
it('returns none, vim and git backends', function()
|
||||
local backends = diff.get_available_backends()
|
||||
table.sort(backends)
|
||||
assert.same({ 'git', 'none', 'vim' }, backends)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('get_backend', function()
|
||||
it('returns vim backend by name', function()
|
||||
local backend = diff.get_backend('vim')
|
||||
assert.is_not_nil(backend)
|
||||
assert.equals('vim', backend.name)
|
||||
end)
|
||||
|
||||
it('returns git backend by name', function()
|
||||
local backend = diff.get_backend('git')
|
||||
assert.is_not_nil(backend)
|
||||
assert.equals('git', backend.name)
|
||||
end)
|
||||
|
||||
it('returns none backend by name', function()
|
||||
local backend = diff.get_backend('none')
|
||||
assert.is_not_nil(backend)
|
||||
assert.equals('none', backend.name)
|
||||
end)
|
||||
|
||||
it('returns nil for invalid name', function()
|
||||
local backend = diff.get_backend('invalid')
|
||||
assert.is_nil(backend)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('get_best_backend', function()
|
||||
it('defaults to vim backend', function()
|
||||
local backend = diff.get_best_backend()
|
||||
assert.equals('vim', backend.name)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('none backend', function()
|
||||
it('returns both expected and actual content', function()
|
||||
local backend = diff.get_backend('none')
|
||||
local result = backend.render('expected\nline2', 'actual\nline2')
|
||||
|
||||
assert.same({
|
||||
expected = { 'expected', 'line2' },
|
||||
actual = { 'actual', 'line2' },
|
||||
}, result.content)
|
||||
assert.same({}, result.highlights)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('vim backend', function()
|
||||
it('returns content as-is', function()
|
||||
local backend = diff.get_backend('vim')
|
||||
local result = backend.render('expected', 'actual')
|
||||
|
||||
assert.same({ 'actual' }, result.content)
|
||||
assert.is_nil(result.highlights)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('is_git_available', function()
|
||||
it('returns boolean without errors', function()
|
||||
local result = diff.is_git_available()
|
||||
assert.equals('boolean', type(result))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('render_diff', function()
|
||||
it('returns result without errors', function()
|
||||
assert.has_no_errors(function()
|
||||
diff.render_diff('expected', 'actual', 'vim')
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
describe('Error boundary handling', function()
|
||||
local cp
|
||||
local state
|
||||
local logged_messages
|
||||
|
||||
before_each(function()
|
||||
logged_messages = {}
|
||||
local mock_logger = {
|
||||
log = function(msg, level)
|
||||
table.insert(logged_messages, { msg = msg, level = level })
|
||||
end,
|
||||
set_config = function() end,
|
||||
}
|
||||
package.loaded['cp.log'] = mock_logger
|
||||
|
||||
package.loaded['cp.scraper'] = {
|
||||
scrape_problem_tests = function(_, contest_id, problem_id, callback)
|
||||
if contest_id == 'fail_scrape' then
|
||||
callback({
|
||||
success = false,
|
||||
error = 'Network error',
|
||||
})
|
||||
return
|
||||
end
|
||||
callback({
|
||||
success = true,
|
||||
problem_id = problem_id,
|
||||
tests = {
|
||||
{ input = '1', expected = '2' },
|
||||
},
|
||||
})
|
||||
end,
|
||||
scrape_contest_metadata = function(_, contest_id, callback)
|
||||
if contest_id == 'fail_scrape' then
|
||||
callback({
|
||||
success = false,
|
||||
error = 'Network error',
|
||||
})
|
||||
return
|
||||
end
|
||||
if contest_id == 'fail_metadata' then
|
||||
callback({
|
||||
success = false,
|
||||
error = 'Contest not found',
|
||||
})
|
||||
return
|
||||
end
|
||||
callback({
|
||||
success = true,
|
||||
problems = {
|
||||
{ id = 'a' },
|
||||
{ id = 'b' },
|
||||
},
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
local cache = require('cp.cache')
|
||||
cache.load = function() end
|
||||
cache.set_test_cases = function() end
|
||||
cache.set_file_state = function() end
|
||||
cache.get_file_state = function()
|
||||
return nil
|
||||
end
|
||||
cache.get_contest_data = function()
|
||||
return nil
|
||||
end
|
||||
cache.get_test_cases = function()
|
||||
return {}
|
||||
end
|
||||
|
||||
if not vim.fn then
|
||||
vim.fn = {}
|
||||
end
|
||||
vim.fn.expand = vim.fn.expand or function()
|
||||
return '/tmp/test.cpp'
|
||||
end
|
||||
vim.fn.mkdir = vim.fn.mkdir or function() end
|
||||
if not vim.api then
|
||||
vim.api = {}
|
||||
end
|
||||
vim.api.nvim_get_current_buf = vim.api.nvim_get_current_buf or function()
|
||||
return 1
|
||||
end
|
||||
vim.api.nvim_buf_get_lines = vim.api.nvim_buf_get_lines
|
||||
or function()
|
||||
return { '' }
|
||||
end
|
||||
if not vim.cmd then
|
||||
vim.cmd = {}
|
||||
end
|
||||
vim.cmd.e = function() end
|
||||
vim.cmd.only = function() end
|
||||
if not vim.system then
|
||||
vim.system = function(_)
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 0 }
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
state = require('cp.state')
|
||||
state.reset()
|
||||
|
||||
cp = require('cp')
|
||||
cp.setup({
|
||||
contests = {
|
||||
codeforces = {
|
||||
default_language = 'cpp',
|
||||
cpp = { extension = 'cpp', test = { 'echo', 'test' } },
|
||||
},
|
||||
},
|
||||
scrapers = { 'codeforces' },
|
||||
})
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
package.loaded['cp.log'] = nil
|
||||
package.loaded['cp.scraper'] = nil
|
||||
if state then
|
||||
state.reset()
|
||||
end
|
||||
end)
|
||||
|
||||
it('should handle scraping failures without state corruption', function()
|
||||
cp.handle_command({ fargs = { 'codeforces', 'fail_scrape', 'a' } })
|
||||
|
||||
vim.wait(100)
|
||||
|
||||
local has_metadata_error = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.msg and log_entry.msg:match('failed to load contest metadata') then
|
||||
has_metadata_error = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(has_metadata_error, 'Should log contest metadata failure')
|
||||
|
||||
assert.equals('codeforces', state.get_platform())
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'run' } })
|
||||
end)
|
||||
end)
|
||||
|
||||
it('should handle missing contest data without crashing navigation', function()
|
||||
state.set_platform('codeforces')
|
||||
state.set_contest_id('nonexistent')
|
||||
state.set_problem_id('a')
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'next' } })
|
||||
end)
|
||||
|
||||
local has_nav_error = false
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.msg and log_entry.msg:match('no contest data available') then
|
||||
has_nav_error = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(has_nav_error, 'Should log navigation error')
|
||||
end)
|
||||
|
||||
it('should handle validation errors without crashing', function()
|
||||
state.reset()
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'next' } })
|
||||
end)
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'prev' } })
|
||||
end)
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'run' } })
|
||||
end)
|
||||
|
||||
local has_validation_error = false
|
||||
local has_appropriate_errors = 0
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if log_entry.msg and log_entry.msg:match('expected string, got nil') then
|
||||
has_validation_error = true
|
||||
elseif
|
||||
log_entry.msg
|
||||
and (log_entry.msg:match('No platform ') or log_entry.msg:match('No contest '))
|
||||
then
|
||||
has_appropriate_errors = has_appropriate_errors + 1
|
||||
end
|
||||
end
|
||||
|
||||
assert.is_false(has_validation_error, 'Should not have validation errors')
|
||||
assert.is_true(has_appropriate_errors > 0, 'Should have user-facing errors')
|
||||
end)
|
||||
|
||||
it('should handle partial state gracefully', function()
|
||||
state.set_platform('codeforces')
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'run' } })
|
||||
end)
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'next' } })
|
||||
end)
|
||||
|
||||
local missing_contest_errors = 0
|
||||
for _, log_entry in ipairs(logged_messages) do
|
||||
if
|
||||
log_entry.msg
|
||||
and (log_entry.msg:match('No problem found') or log_entry.msg:match('No contest'))
|
||||
then
|
||||
missing_contest_errors = missing_contest_errors + 1
|
||||
end
|
||||
end
|
||||
assert.is_true(missing_contest_errors > 0, 'Should report missing contest')
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,429 +0,0 @@
|
|||
describe('cp.execute', function()
|
||||
local execute
|
||||
local mock_system_calls
|
||||
local temp_files
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
execute = require('cp.runner.execute')
|
||||
mock_system_calls = {}
|
||||
temp_files = {}
|
||||
|
||||
vim.system = function(cmd, opts)
|
||||
table.insert(mock_system_calls, { cmd = cmd, opts = opts })
|
||||
if vim.tbl_isempty(cmd) then
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 0, stdout = '', stderr = '' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local result = { code = 0, stdout = '', stderr = '' }
|
||||
|
||||
if cmd[1] == 'mkdir' then
|
||||
result = { code = 0 }
|
||||
elseif cmd[1] == 'g++' or cmd[1] == 'gcc' then
|
||||
result = { code = 0, stderr = '' }
|
||||
elseif cmd[1]:match('%.run$') or cmd[1] == 'python' then
|
||||
result = { code = 0, stdout = '42\n', stderr = '' }
|
||||
end
|
||||
|
||||
return {
|
||||
wait = function()
|
||||
return result
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local original_fn = vim.fn
|
||||
vim.fn = vim.tbl_extend('force', vim.fn, {
|
||||
filereadable = function(path)
|
||||
return temp_files[path] and 1 or 0
|
||||
end,
|
||||
readfile = function(path)
|
||||
return temp_files[path] or {}
|
||||
end,
|
||||
fnamemodify = function(path, modifier)
|
||||
if modifier == ':e' then
|
||||
return path:match('%.([^.]+)$') or ''
|
||||
end
|
||||
return original_fn.fnamemodify(path, modifier)
|
||||
end,
|
||||
})
|
||||
|
||||
vim.uv = vim.tbl_extend('force', vim.uv or {}, {
|
||||
hrtime = function()
|
||||
return 1000000000
|
||||
end,
|
||||
})
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
vim.system = vim.system_original or vim.system
|
||||
spec_helper.teardown()
|
||||
temp_files = {}
|
||||
end)
|
||||
|
||||
describe('template substitution', function()
|
||||
it('substitutes placeholders correctly', function()
|
||||
local language_config = {
|
||||
compile = { 'g++', '{source_file}', '-o', '{binary_file}' },
|
||||
}
|
||||
local substitutions = {
|
||||
source_file = 'test.cpp',
|
||||
binary_file = 'test.run',
|
||||
}
|
||||
|
||||
local result = execute.compile_generic(language_config, substitutions)
|
||||
|
||||
assert.equals(0, result.code)
|
||||
assert.is_true(#mock_system_calls > 0)
|
||||
|
||||
local compile_call = mock_system_calls[1]
|
||||
assert.equals('sh', compile_call.cmd[1])
|
||||
assert.equals('-c', compile_call.cmd[2])
|
||||
assert.is_not_nil(string.find(compile_call.cmd[3], 'g%+%+ test%.cpp %-o test%.run'))
|
||||
assert.is_not_nil(string.find(compile_call.cmd[3], '2>&1'))
|
||||
end)
|
||||
|
||||
it('handles multiple substitutions in single argument', function()
|
||||
local language_config = {
|
||||
compile = { 'g++', '{source_file}', '-o{binary_file}' },
|
||||
}
|
||||
local substitutions = {
|
||||
source_file = 'main.cpp',
|
||||
binary_file = 'main.out',
|
||||
}
|
||||
|
||||
execute.compile_generic(language_config, substitutions)
|
||||
|
||||
local compile_call = mock_system_calls[1]
|
||||
assert.is_not_nil(string.find(compile_call.cmd[3], '%-omain%.out'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('compilation', function()
|
||||
it('skips compilation when not required', function()
|
||||
local language_config = {}
|
||||
local substitutions = {}
|
||||
|
||||
local result = execute.compile_generic(language_config, substitutions)
|
||||
|
||||
assert.equals(0, result.code)
|
||||
assert.equals('', result.stderr)
|
||||
assert.equals(0, #mock_system_calls)
|
||||
end)
|
||||
|
||||
it('compiles cpp files correctly', function()
|
||||
local language_config = {
|
||||
compile = { 'g++', '{source_file}', '-o', '{binary_file}', '-std=c++17' },
|
||||
}
|
||||
local substitutions = {
|
||||
source_file = 'solution.cpp',
|
||||
binary_file = 'build/solution.run',
|
||||
}
|
||||
|
||||
local result = execute.compile_generic(language_config, substitutions)
|
||||
|
||||
assert.equals(0, result.code)
|
||||
assert.is_true(#mock_system_calls > 0)
|
||||
|
||||
local compile_call = mock_system_calls[1]
|
||||
assert.equals('sh', compile_call.cmd[1])
|
||||
assert.is_not_nil(string.find(compile_call.cmd[3], '%-std=c%+%+17'))
|
||||
end)
|
||||
|
||||
it('handles compilation errors gracefully', function()
|
||||
vim.system = function()
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 1, stderr = 'error: undefined variable' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local language_config = {
|
||||
compile = { 'g++', '{source_file}', '-o', '{binary_file}' },
|
||||
}
|
||||
local substitutions = { source_file = 'bad.cpp', binary_file = 'bad.run' }
|
||||
|
||||
local result = execute.compile_generic(language_config, substitutions)
|
||||
|
||||
assert.equals(1, result.code)
|
||||
assert.is_not_nil(result.stderr:match('undefined variable'))
|
||||
end)
|
||||
|
||||
it('measures compilation time', function()
|
||||
local start_time = 1000000000
|
||||
local end_time = 1500000000
|
||||
local call_count = 0
|
||||
|
||||
vim.uv.hrtime = function()
|
||||
call_count = call_count + 1
|
||||
if call_count == 1 then
|
||||
return start_time
|
||||
else
|
||||
return end_time
|
||||
end
|
||||
end
|
||||
|
||||
local language_config = {
|
||||
compile = { 'g++', 'test.cpp', '-o', 'test.run' },
|
||||
}
|
||||
|
||||
execute.compile_generic(language_config, {})
|
||||
assert.is_true(call_count >= 2)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('test execution', function()
|
||||
it('executes commands with input data', function()
|
||||
vim.system = function(cmd, opts)
|
||||
table.insert(mock_system_calls, { cmd = cmd, opts = opts })
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 0, stdout = '3\n', stderr = '' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local language_config = {
|
||||
run = { '{binary_file}' },
|
||||
}
|
||||
|
||||
execute.compile_generic(language_config, { binary_file = './test.run' })
|
||||
end)
|
||||
|
||||
it('handles command execution', function()
|
||||
vim.system = function(_, opts)
|
||||
if opts then
|
||||
assert.equals(false, opts.text)
|
||||
end
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 124, stdout = '', stderr = '' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local language_config = {
|
||||
compile = { 'timeout', '1', 'sleep', '2' },
|
||||
}
|
||||
|
||||
local result = execute.compile_generic(language_config, {})
|
||||
assert.equals(124, result.code)
|
||||
end)
|
||||
|
||||
it('captures stderr output', function()
|
||||
vim.system = function()
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 1, stdout = '', stderr = 'runtime error\n' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local language_config = {
|
||||
compile = { 'false' },
|
||||
}
|
||||
|
||||
local result = execute.compile_generic(language_config, {})
|
||||
assert.equals(1, result.code)
|
||||
assert.is_not_nil(result.stderr:match('runtime error'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('directory creation', function()
|
||||
it('creates build and io directories', function()
|
||||
local language_config = {
|
||||
compile = { 'mkdir', '-p', 'build', 'io' },
|
||||
}
|
||||
|
||||
execute.compile_generic(language_config, {})
|
||||
|
||||
local mkdir_call = mock_system_calls[1]
|
||||
assert.equals('sh', mkdir_call.cmd[1])
|
||||
assert.is_not_nil(string.find(mkdir_call.cmd[3], 'mkdir'))
|
||||
assert.is_not_nil(string.find(mkdir_call.cmd[3], 'build'))
|
||||
assert.is_not_nil(string.find(mkdir_call.cmd[3], 'io'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('language detection', function()
|
||||
it('detects cpp from extension', function()
|
||||
vim.fn.fnamemodify = function()
|
||||
return 'cpp'
|
||||
end
|
||||
|
||||
assert.has_no_errors(function()
|
||||
execute.compile_generic({}, {})
|
||||
end)
|
||||
end)
|
||||
|
||||
it('falls back to default language', function()
|
||||
vim.fn.fnamemodify = function(_, modifier)
|
||||
if modifier == ':e' then
|
||||
return 'unknown'
|
||||
end
|
||||
return ''
|
||||
end
|
||||
|
||||
assert.has_no_errors(function()
|
||||
execute.compile_generic({}, {})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('edge cases', function()
|
||||
it('handles empty command templates', function()
|
||||
local language_config = {
|
||||
compile = {},
|
||||
}
|
||||
|
||||
local result = execute.compile_generic(language_config, {})
|
||||
assert.equals(0, result.code)
|
||||
end)
|
||||
|
||||
it('handles commands with no substitutions needed', function()
|
||||
local language_config = {
|
||||
compile = { 'echo', 'hello' },
|
||||
}
|
||||
|
||||
local result = execute.compile_generic(language_config, {})
|
||||
assert.equals(0, result.code)
|
||||
|
||||
local echo_call = mock_system_calls[1]
|
||||
assert.equals('sh', echo_call.cmd[1])
|
||||
assert.is_not_nil(string.find(echo_call.cmd[3], 'echo hello'))
|
||||
end)
|
||||
|
||||
it('handles multiple consecutive substitutions', function()
|
||||
local language_config = {
|
||||
compile = { '{compiler}{compiler}', '{file}{file}' },
|
||||
}
|
||||
local substitutions = {
|
||||
compiler = 'g++',
|
||||
file = 'test.cpp',
|
||||
}
|
||||
|
||||
execute.compile_generic(language_config, substitutions)
|
||||
|
||||
local call = mock_system_calls[1]
|
||||
assert.equals('sh', call.cmd[1])
|
||||
assert.is_not_nil(string.find(call.cmd[3], 'g%+%+g%+%+ test%.cpptest%.cpp'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('stderr/stdout redirection', function()
|
||||
it('should use stderr redirection (2>&1)', function()
|
||||
local original_system = vim.system
|
||||
local captured_command = nil
|
||||
|
||||
vim.system = function(cmd, _)
|
||||
captured_command = cmd
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 0, stdout = '', stderr = '' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local language_config = {
|
||||
compile = { 'g++', '-std=c++17', '-o', '{binary}', '{source}' },
|
||||
}
|
||||
local substitutions = { source = 'test.cpp', binary = 'build/test', version = '17' }
|
||||
execute.compile_generic(language_config, substitutions)
|
||||
|
||||
assert.is_not_nil(captured_command)
|
||||
assert.equals('sh', captured_command[1])
|
||||
assert.equals('-c', captured_command[2])
|
||||
assert.is_not_nil(
|
||||
string.find(captured_command[3], '2>&1'),
|
||||
'Command should contain 2>&1 redirection'
|
||||
)
|
||||
|
||||
vim.system = original_system
|
||||
end)
|
||||
|
||||
it('should return combined stdout+stderr in result', function()
|
||||
local original_system = vim.system
|
||||
local test_output = 'STDOUT: Hello\nSTDERR: Error message\n'
|
||||
|
||||
vim.system = function(_, _)
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 1, stdout = test_output, stderr = '' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local language_config = {
|
||||
compile = { 'g++', '-std=c++17', '-o', '{binary}', '{source}' },
|
||||
}
|
||||
local substitutions = { source = 'test.cpp', binary = 'build/test', version = '17' }
|
||||
local result = execute.compile_generic(language_config, substitutions)
|
||||
|
||||
assert.equals(1, result.code)
|
||||
assert.equals(test_output, result.stdout)
|
||||
|
||||
vim.system = original_system
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('integration with execute_command function', function()
|
||||
it('tests the full execute_command flow with stderr/stdout combination', function()
|
||||
local cmd = { 'echo', 'test output' }
|
||||
local input_data = 'test input'
|
||||
local timeout_ms = 1000
|
||||
|
||||
local original_system = vim.system
|
||||
vim.system = function(shell_cmd, opts)
|
||||
assert.equals('sh', shell_cmd[1])
|
||||
assert.equals('-c', shell_cmd[2])
|
||||
assert.is_not_nil(string.find(shell_cmd[3], '2>&1'))
|
||||
assert.equals(input_data, opts.stdin)
|
||||
assert.equals(timeout_ms, opts.timeout)
|
||||
assert.is_true(opts.text)
|
||||
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 0, stdout = 'combined output from stdout and stderr', stderr = '' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local execute_command = require('cp.runner.execute').execute_command
|
||||
or function(command, stdin_data, timeout)
|
||||
local redirected_cmd = vim.deepcopy(command)
|
||||
if #redirected_cmd > 0 then
|
||||
redirected_cmd[#redirected_cmd] = redirected_cmd[#redirected_cmd] .. ' 2>&1'
|
||||
end
|
||||
|
||||
local result = vim
|
||||
.system({ 'sh', '-c', table.concat(redirected_cmd, ' ') }, {
|
||||
stdin = stdin_data,
|
||||
timeout = timeout,
|
||||
text = true,
|
||||
})
|
||||
:wait()
|
||||
|
||||
return {
|
||||
stdout = result.stdout or '',
|
||||
stderr = result.stderr or '',
|
||||
code = result.code or 0,
|
||||
time_ms = 0,
|
||||
timed_out = result.code == 124,
|
||||
}
|
||||
end
|
||||
|
||||
local result = execute_command(cmd, input_data, timeout_ms)
|
||||
|
||||
assert.equals(0, result.code)
|
||||
assert.equals('combined output from stdout and stderr', result.stdout)
|
||||
|
||||
vim.system = original_system
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
describe('cp.fzf_lua', function()
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
|
||||
package.preload['fzf-lua'] = function()
|
||||
return {
|
||||
fzf_exec = function(_, _) end,
|
||||
}
|
||||
end
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
end)
|
||||
|
||||
describe('module loading', function()
|
||||
it('loads fzf-lua integration without error', function()
|
||||
assert.has_no_errors(function()
|
||||
require('cp.pickers.fzf_lua')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('returns module with picker function', function()
|
||||
local fzf_lua_cp = require('cp.pickers.fzf_lua')
|
||||
assert.is_table(fzf_lua_cp)
|
||||
assert.is_function(fzf_lua_cp.pick)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('basic running', function()
|
||||
it('can run and open the picker with :CP pick', function()
|
||||
local cp = require('cp')
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'pick' } })
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
describe('cp.highlight', function()
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
local highlight
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
highlight = require('cp.ui.highlight')
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
end)
|
||||
|
||||
describe('parse_git_diff', function()
|
||||
it('skips git diff headers', function()
|
||||
local diff_output = [[diff --git a/test b/test
|
||||
index 1234567..abcdefg 100644
|
||||
--- a/test
|
||||
+++ b/test
|
||||
@@ -1,3 +1,3 @@
|
||||
hello
|
||||
+world
|
||||
-goodbye]]
|
||||
local result = highlight.parse_git_diff(diff_output)
|
||||
assert.same({ 'hello', 'world' }, result.content)
|
||||
end)
|
||||
|
||||
it('processes added lines', function()
|
||||
local diff_output = '+hello w{+o+}rld'
|
||||
local result = highlight.parse_git_diff(diff_output)
|
||||
assert.same({ 'hello world' }, result.content)
|
||||
assert.equals(1, #result.highlights)
|
||||
assert.equals('CpDiffAdded', result.highlights[1].highlight_group)
|
||||
end)
|
||||
|
||||
it('ignores removed lines', function()
|
||||
local diff_output = 'hello\n-removed line\n+kept line'
|
||||
local result = highlight.parse_git_diff(diff_output)
|
||||
assert.same({ 'hello', 'kept line' }, result.content)
|
||||
end)
|
||||
|
||||
it('handles unchanged lines', function()
|
||||
local diff_output = 'unchanged line\n+added line'
|
||||
local result = highlight.parse_git_diff(diff_output)
|
||||
assert.same({ 'unchanged line', 'added line' }, result.content)
|
||||
end)
|
||||
|
||||
it('sets correct line numbers', function()
|
||||
local diff_output = '+first {+added+}\n+second {+text+}'
|
||||
local result = highlight.parse_git_diff(diff_output)
|
||||
assert.equals(0, result.highlights[1].line)
|
||||
assert.equals(1, result.highlights[2].line)
|
||||
end)
|
||||
|
||||
it('handles empty diff output', function()
|
||||
local result = highlight.parse_git_diff('')
|
||||
assert.same({}, result.content)
|
||||
assert.same({}, result.highlights)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('apply_highlights', function()
|
||||
it('handles empty highlights without errors', function()
|
||||
local namespace = highlight.create_namespace()
|
||||
assert.has_no_errors(function()
|
||||
highlight.apply_highlights(1, {}, namespace)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('handles valid highlight data without errors', function()
|
||||
vim.api.nvim_buf_set_lines(1, 0, -1, false, { 'hello world test line' })
|
||||
local highlights = {
|
||||
{
|
||||
line = 0,
|
||||
col_start = 5,
|
||||
col_end = 10,
|
||||
highlight_group = 'CpDiffAdded',
|
||||
},
|
||||
}
|
||||
local namespace = highlight.create_namespace()
|
||||
assert.has_no_errors(function()
|
||||
highlight.apply_highlights(1, highlights, namespace)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('create_namespace', function()
|
||||
it('returns a number', function()
|
||||
local result = highlight.create_namespace()
|
||||
assert.equals('number', type(result))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('parse_and_apply_diff', function()
|
||||
it('returns content lines', function()
|
||||
local namespace = highlight.create_namespace()
|
||||
local result = highlight.parse_and_apply_diff(1, '+first\n+second', namespace)
|
||||
assert.same({ 'first', 'second' }, result)
|
||||
end)
|
||||
|
||||
it('handles empty diff', function()
|
||||
local namespace = highlight.create_namespace()
|
||||
local result = highlight.parse_and_apply_diff(1, '', namespace)
|
||||
assert.same({}, result)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
describe('Panel integration', function()
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
local cp
|
||||
local state
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup_full()
|
||||
spec_helper.mock_scraper_success()
|
||||
|
||||
state = require('cp.state')
|
||||
state.reset()
|
||||
|
||||
local mock_async_setup = {
|
||||
setup_contest_async = function() end,
|
||||
handle_full_setup_async = function(cmd)
|
||||
state.set_platform(cmd.platform)
|
||||
state.set_contest_id(cmd.contest)
|
||||
state.set_problem_id(cmd.problem)
|
||||
end,
|
||||
setup_problem_async = function() end,
|
||||
}
|
||||
package.loaded['cp.async.setup'] = mock_async_setup
|
||||
|
||||
local mock_setup = {
|
||||
set_platform = function(platform)
|
||||
state.set_platform(platform)
|
||||
return true
|
||||
end,
|
||||
setup_contest = function(platform, contest, problem, _)
|
||||
state.set_platform(platform)
|
||||
state.set_contest_id(contest)
|
||||
if problem then
|
||||
state.set_problem_id(problem)
|
||||
end
|
||||
end,
|
||||
setup_problem = function() end,
|
||||
navigate_problem = function() end,
|
||||
}
|
||||
package.loaded['cp.setup'] = mock_setup
|
||||
|
||||
cp = require('cp')
|
||||
cp.setup({
|
||||
contests = {
|
||||
codeforces = {
|
||||
default_language = 'cpp',
|
||||
cpp = { extension = 'cpp', test = { 'echo', 'test' } },
|
||||
},
|
||||
},
|
||||
scrapers = { 'codeforces' },
|
||||
})
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
if state then
|
||||
state.reset()
|
||||
end
|
||||
end)
|
||||
|
||||
it('should handle run command with properly set contest context', function()
|
||||
cp.handle_command({ fargs = { 'codeforces', '2146', 'b' } })
|
||||
|
||||
assert.equals('codeforces', state.get_platform())
|
||||
assert.equals('2146', state.get_contest_id())
|
||||
assert.equals('b', state.get_problem_id())
|
||||
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'run' } })
|
||||
end)
|
||||
|
||||
local has_validation_error = false
|
||||
for _, log_entry in ipairs(spec_helper.logged_messages) do
|
||||
if
|
||||
log_entry.level == vim.log.levels.ERROR
|
||||
and log_entry.msg:match('expected string, got nil')
|
||||
then
|
||||
has_validation_error = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_false(has_validation_error)
|
||||
end)
|
||||
|
||||
it('should handle state module interface correctly', function()
|
||||
local run = require('cp.runner.run')
|
||||
|
||||
state.set_platform('codeforces')
|
||||
state.set_contest_id('2146')
|
||||
state.set_problem_id('b')
|
||||
|
||||
local config_module = require('cp.config')
|
||||
config_module.setup({
|
||||
contests = { codeforces = { cpp = { extension = 'cpp' } } },
|
||||
})
|
||||
local cp_state = require('cp.state')
|
||||
cp_state.set_platform('codeforces')
|
||||
cp_state.set_contest_id('2146')
|
||||
cp_state.set_problem_id('b')
|
||||
|
||||
assert.has_no_errors(function()
|
||||
run.load_test_cases(state)
|
||||
end)
|
||||
|
||||
local fake_state_data = { platform = 'codeforces', contest_id = '2146', problem_id = 'b' }
|
||||
assert.has_errors(function()
|
||||
run.load_test_cases(fake_state_data)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
describe('cp.picker', function()
|
||||
local picker
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
picker = require('cp.pickers')
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
end)
|
||||
|
||||
describe('get_contests_for_platform', function()
|
||||
it('returns empty list when scraper fails', function()
|
||||
vim.system = function(_, _)
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 1, stderr = 'test error' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local contests = picker.get_contests_for_platform('test_platform')
|
||||
assert.is_table(contests)
|
||||
assert.equals(0, #contests)
|
||||
end)
|
||||
|
||||
it('returns empty list when JSON is invalid', function()
|
||||
vim.system = function(_, _)
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 0, stdout = 'invalid json' }
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local contests = picker.get_contests_for_platform('test_platform')
|
||||
assert.is_table(contests)
|
||||
assert.equals(0, #contests)
|
||||
end)
|
||||
|
||||
it('returns contest list when scraper succeeds', function()
|
||||
local cache = require('cp.cache')
|
||||
local utils = require('cp.utils')
|
||||
|
||||
cache.load = function() end
|
||||
cache.get_contest_list = function()
|
||||
return nil
|
||||
end
|
||||
cache.set_contest_list = function() end
|
||||
|
||||
utils.setup_python_env = function()
|
||||
return true
|
||||
end
|
||||
utils.get_plugin_path = function()
|
||||
return '/test/path'
|
||||
end
|
||||
|
||||
vim.system = function(_, _)
|
||||
return {
|
||||
wait = function()
|
||||
return {
|
||||
code = 0,
|
||||
stdout = vim.json.encode({
|
||||
success = true,
|
||||
contests = {
|
||||
{
|
||||
id = 'abc123',
|
||||
name = 'AtCoder Beginner Contest 123',
|
||||
display_name = 'Beginner Contest 123 (ABC)',
|
||||
},
|
||||
{
|
||||
id = '1951',
|
||||
name = 'Educational Round 168',
|
||||
display_name = 'Educational Round 168',
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
local contests = picker.get_contests_for_platform('test_platform')
|
||||
assert.is_table(contests)
|
||||
assert.equals(2, #contests)
|
||||
assert.equals('abc123', contests[1].id)
|
||||
assert.equals('AtCoder Beginner Contest 123', contests[1].name)
|
||||
assert.equals('Beginner Contest 123 (ABC)', contests[1].display_name)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
describe('cp.run_render', function()
|
||||
local run_render = require('cp.runner.run_render')
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
end)
|
||||
|
||||
describe('get_status_info', function()
|
||||
it('returns AC for pass status', function()
|
||||
local test_case = { status = 'pass' }
|
||||
local result = run_render.get_status_info(test_case)
|
||||
assert.equals('AC', result.text)
|
||||
assert.equals('CpTestAC', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns WA for fail status with normal exit codes', function()
|
||||
local test_case = { status = 'fail', code = 1 }
|
||||
local result = run_render.get_status_info(test_case)
|
||||
assert.equals('WA', result.text)
|
||||
assert.equals('CpTestWA', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns TLE for timeout status', function()
|
||||
local test_case = { status = 'timeout' }
|
||||
local result = run_render.get_status_info(test_case)
|
||||
assert.equals('TLE', result.text)
|
||||
assert.equals('CpTestTLE', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns TLE for timed out fail status', function()
|
||||
local test_case = { status = 'fail', timed_out = true }
|
||||
local result = run_render.get_status_info(test_case)
|
||||
assert.equals('TLE', result.text)
|
||||
assert.equals('CpTestTLE', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns RTE for fail with signal codes (>= 128)', function()
|
||||
local test_case = { status = 'fail', code = 139 }
|
||||
local result = run_render.get_status_info(test_case)
|
||||
assert.equals('RTE', result.text)
|
||||
assert.equals('CpTestRTE', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns empty for pending status', function()
|
||||
local test_case = { status = 'pending' }
|
||||
local result = run_render.get_status_info(test_case)
|
||||
assert.equals('', result.text)
|
||||
assert.equals('CpTestPending', result.highlight_group)
|
||||
end)
|
||||
|
||||
it('returns running indicator for running status', function()
|
||||
local test_case = { status = 'running' }
|
||||
local result = run_render.get_status_info(test_case)
|
||||
assert.equals('...', result.text)
|
||||
assert.equals('CpTestPending', result.highlight_group)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('render_test_list', function()
|
||||
it('renders table with headers and borders', function()
|
||||
local test_state = {
|
||||
test_cases = {
|
||||
{ status = 'pass', input = '5' },
|
||||
{ status = 'fail', code = 1, input = '3' },
|
||||
},
|
||||
current_index = 1,
|
||||
}
|
||||
local result = run_render.render_test_list(test_state)
|
||||
assert.is_true(result[1]:find('^┌') ~= nil)
|
||||
assert.is_true(result[2]:find('│.*#.*│.*Status.*│.*Time.*│.*Exit Code.*│') ~= nil)
|
||||
assert.is_true(result[3]:find('^├') ~= nil)
|
||||
end)
|
||||
|
||||
it('shows current test with > prefix in table', function()
|
||||
local test_state = {
|
||||
test_cases = {
|
||||
{ status = 'pass', input = '' },
|
||||
{ status = 'pass', input = '' },
|
||||
},
|
||||
current_index = 2,
|
||||
}
|
||||
local result = run_render.render_test_list(test_state)
|
||||
local found_current = false
|
||||
for _, line in ipairs(result) do
|
||||
if line:match('│.*> 2.*│') then
|
||||
found_current = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(found_current)
|
||||
end)
|
||||
|
||||
it('displays input only for current test', function()
|
||||
local test_state = {
|
||||
test_cases = {
|
||||
{ status = 'pass', input = '5 3' },
|
||||
{ status = 'pass', input = '2 4' },
|
||||
},
|
||||
current_index = 1,
|
||||
}
|
||||
local result = run_render.render_test_list(test_state)
|
||||
local found_input = false
|
||||
for _, line in ipairs(result) do
|
||||
if line:match('│5 3') then
|
||||
found_input = true
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(found_input)
|
||||
end)
|
||||
|
||||
it('handles empty test cases', function()
|
||||
local test_state = { test_cases = {}, current_index = 1 }
|
||||
local result = run_render.render_test_list(test_state)
|
||||
assert.equals(3, #result)
|
||||
end)
|
||||
|
||||
it('preserves input line breaks', function()
|
||||
local test_state = {
|
||||
test_cases = {
|
||||
{ status = 'pass', input = '5\n3\n1' },
|
||||
},
|
||||
current_index = 1,
|
||||
}
|
||||
local result = run_render.render_test_list(test_state)
|
||||
local input_lines = {}
|
||||
for _, line in ipairs(result) do
|
||||
if line:match('^│[531]') then
|
||||
table.insert(input_lines, line:match('│([531])'))
|
||||
end
|
||||
end
|
||||
assert.same({ '5', '3', '1' }, input_lines)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('render_status_bar', function()
|
||||
it('formats time and exit code', function()
|
||||
local test_case = { time_ms = 45.7, code = 0 }
|
||||
local result = run_render.render_status_bar(test_case)
|
||||
assert.equals('45.70ms │ Exit: 0', result)
|
||||
end)
|
||||
|
||||
it('handles missing time', function()
|
||||
local test_case = { code = 0 }
|
||||
local result = run_render.render_status_bar(test_case)
|
||||
assert.equals('Exit: 0', result)
|
||||
end)
|
||||
|
||||
it('handles missing exit code', function()
|
||||
local test_case = { time_ms = 123 }
|
||||
local result = run_render.render_status_bar(test_case)
|
||||
assert.equals('123.00ms', result)
|
||||
end)
|
||||
|
||||
it('returns empty for nil test case', function()
|
||||
local result = run_render.render_status_bar(nil)
|
||||
assert.equals('', result)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('setup_highlights', function()
|
||||
it('runs without errors', function()
|
||||
assert.has_no_errors(function()
|
||||
run_render.setup_highlights()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('highlight positioning', function()
|
||||
it('generates correct highlight positions for status text', function()
|
||||
local test_state = {
|
||||
test_cases = {
|
||||
{ status = 'pass', input = '' },
|
||||
{ status = 'fail', code = 1, input = '' },
|
||||
},
|
||||
current_index = 1,
|
||||
}
|
||||
local lines, highlights = run_render.render_test_list(test_state)
|
||||
|
||||
assert.equals(2, #highlights)
|
||||
|
||||
for _, hl in ipairs(highlights) do
|
||||
assert.is_not_nil(hl.line)
|
||||
assert.is_not_nil(hl.col_start)
|
||||
assert.is_not_nil(hl.col_end)
|
||||
assert.is_not_nil(hl.highlight_group)
|
||||
assert.is_true(hl.col_end > hl.col_start)
|
||||
|
||||
local line_content = lines[hl.line + 1]
|
||||
local highlighted_text = line_content:sub(hl.col_start + 1, hl.col_end)
|
||||
assert.is_true(highlighted_text == 'AC' or highlighted_text == 'WA')
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
describe('run module', function()
|
||||
local run = require('cp.runner.run')
|
||||
|
||||
describe('basic functionality', function()
|
||||
it('has required functions', function()
|
||||
assert.is_function(run.load_test_cases)
|
||||
assert.is_function(run.run_test_case)
|
||||
assert.is_function(run.run_all_test_cases)
|
||||
assert.is_function(run.get_run_panel_state)
|
||||
assert.is_function(run.handle_compilation_failure)
|
||||
end)
|
||||
|
||||
it('can get panel state', function()
|
||||
local state = run.get_run_panel_state()
|
||||
assert.is_table(state)
|
||||
assert.is_table(state.test_cases)
|
||||
end)
|
||||
|
||||
it('handles compilation failure', function()
|
||||
local compilation_output = 'error.cpp:1:1: error: undefined variable'
|
||||
|
||||
assert.does_not_error(function()
|
||||
run.handle_compilation_failure(compilation_output)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
describe('cp.snippets', function()
|
||||
local snippets
|
||||
local mock_luasnip
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
snippets = spec_helper.fresh_require('cp.snippets')
|
||||
mock_luasnip = {
|
||||
snippet = function(trigger, body)
|
||||
return { trigger = trigger, body = body }
|
||||
end,
|
||||
insert_node = function(pos)
|
||||
return { type = 'insert', pos = pos }
|
||||
end,
|
||||
add_snippets = function(filetype, snippet_list)
|
||||
mock_luasnip.added = mock_luasnip.added or {}
|
||||
mock_luasnip.added[filetype] = snippet_list
|
||||
end,
|
||||
added = {},
|
||||
}
|
||||
|
||||
mock_luasnip.extras = {
|
||||
fmt = {
|
||||
fmt = function(template, nodes)
|
||||
return { template = template, nodes = nodes }
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
package.loaded['luasnip'] = mock_luasnip
|
||||
package.loaded['luasnip.extras.fmt'] = mock_luasnip.extras.fmt
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
package.loaded['cp.snippets'] = nil
|
||||
package.loaded['luasnip'] = nil
|
||||
package.loaded['luasnip.extras.fmt'] = nil
|
||||
end)
|
||||
|
||||
describe('setup without luasnip', function()
|
||||
it('handles missing luasnip gracefully', function()
|
||||
package.loaded['luasnip'] = nil
|
||||
|
||||
assert.has_no_errors(function()
|
||||
snippets.setup({})
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('setup with luasnip available', function()
|
||||
it('sets up default cpp snippets for all contests', function()
|
||||
local config = { snippets = {} }
|
||||
|
||||
snippets.setup(config)
|
||||
|
||||
assert.is_not_nil(mock_luasnip.added.cpp)
|
||||
assert.is_true(#mock_luasnip.added.cpp >= 3)
|
||||
|
||||
local triggers = {}
|
||||
for _, snippet in ipairs(mock_luasnip.added.cpp) do
|
||||
table.insert(triggers, snippet.trigger)
|
||||
end
|
||||
|
||||
assert.is_true(vim.tbl_contains(triggers, 'cp.nvim/codeforces.cpp'))
|
||||
assert.is_true(vim.tbl_contains(triggers, 'cp.nvim/atcoder.cpp'))
|
||||
assert.is_true(vim.tbl_contains(triggers, 'cp.nvim/cses.cpp'))
|
||||
end)
|
||||
|
||||
it('sets up default python snippets for all contests', function()
|
||||
local config = { snippets = {} }
|
||||
|
||||
snippets.setup(config)
|
||||
|
||||
assert.is_not_nil(mock_luasnip.added.python)
|
||||
assert.is_true(#mock_luasnip.added.python >= 3)
|
||||
|
||||
local triggers = {}
|
||||
for _, snippet in ipairs(mock_luasnip.added.python) do
|
||||
table.insert(triggers, snippet.trigger)
|
||||
end
|
||||
|
||||
assert.is_true(vim.tbl_contains(triggers, 'cp.nvim/codeforces.python'))
|
||||
assert.is_true(vim.tbl_contains(triggers, 'cp.nvim/atcoder.python'))
|
||||
assert.is_true(vim.tbl_contains(triggers, 'cp.nvim/cses.python'))
|
||||
end)
|
||||
|
||||
it('includes template content with placeholders', function()
|
||||
local config = { snippets = {} }
|
||||
|
||||
snippets.setup(config)
|
||||
|
||||
local cpp_snippets = mock_luasnip.added.cpp or {}
|
||||
local codeforces_snippet = nil
|
||||
for _, snippet in ipairs(cpp_snippets) do
|
||||
if snippet.trigger == 'cp.nvim/codeforces.cpp' then
|
||||
codeforces_snippet = snippet
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
assert.is_not_nil(codeforces_snippet)
|
||||
assert.is_not_nil(codeforces_snippet.body)
|
||||
assert.equals('table', type(codeforces_snippet.body))
|
||||
assert.is_not_nil(codeforces_snippet.body.template:match('#include'))
|
||||
assert.is_not_nil(codeforces_snippet.body.template:match('void solve'))
|
||||
end)
|
||||
|
||||
it('respects user snippet overrides', function()
|
||||
local custom_snippet = {
|
||||
trigger = 'cp.nvim/custom.cpp',
|
||||
body = 'custom template',
|
||||
}
|
||||
local config = {
|
||||
snippets = { custom_snippet },
|
||||
}
|
||||
|
||||
snippets.setup(config)
|
||||
|
||||
local cpp_snippets = mock_luasnip.added.cpp or {}
|
||||
local found_custom = false
|
||||
for _, snippet in ipairs(cpp_snippets) do
|
||||
if snippet.trigger == 'cp.nvim/custom.cpp' then
|
||||
found_custom = true
|
||||
assert.equals('custom template', snippet.body)
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(found_custom)
|
||||
end)
|
||||
|
||||
it('filters user snippets by language', function()
|
||||
local cpp_snippet = {
|
||||
trigger = 'cp.nvim/custom.cpp',
|
||||
body = 'cpp template',
|
||||
}
|
||||
local python_snippet = {
|
||||
trigger = 'cp.nvim/custom.python',
|
||||
body = 'python template',
|
||||
}
|
||||
local config = {
|
||||
snippets = { cpp_snippet, python_snippet },
|
||||
}
|
||||
|
||||
snippets.setup(config)
|
||||
|
||||
local cpp_snippets = mock_luasnip.added.cpp or {}
|
||||
local python_snippets = mock_luasnip.added.python or {}
|
||||
|
||||
local cpp_has_custom = false
|
||||
for _, snippet in ipairs(cpp_snippets) do
|
||||
if snippet.trigger == 'cp.nvim/custom.cpp' then
|
||||
cpp_has_custom = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local python_has_custom = false
|
||||
for _, snippet in ipairs(python_snippets) do
|
||||
if snippet.trigger == 'cp.nvim/custom.python' then
|
||||
python_has_custom = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
assert.is_true(cpp_has_custom)
|
||||
assert.is_true(python_has_custom)
|
||||
end)
|
||||
|
||||
it('handles empty config gracefully', function()
|
||||
assert.has_no_errors(function()
|
||||
snippets.setup({})
|
||||
end)
|
||||
|
||||
assert.is_not_nil(mock_luasnip.added.cpp)
|
||||
assert.is_not_nil(mock_luasnip.added.python)
|
||||
end)
|
||||
|
||||
it('handles empty config gracefully', function()
|
||||
assert.has_no_errors(function()
|
||||
snippets.setup({ snippets = {} })
|
||||
end)
|
||||
end)
|
||||
|
||||
it('creates templates for correct filetypes', function()
|
||||
local config = { snippets = {} }
|
||||
|
||||
snippets.setup(config)
|
||||
|
||||
assert.is_not_nil(mock_luasnip.added.cpp)
|
||||
assert.is_not_nil(mock_luasnip.added.python)
|
||||
assert.is_nil(mock_luasnip.added.c)
|
||||
assert.is_nil(mock_luasnip.added.py)
|
||||
end)
|
||||
|
||||
it('excludes overridden default snippets', function()
|
||||
local override_snippet = {
|
||||
trigger = 'cp.nvim/codeforces.cpp',
|
||||
body = 'overridden template',
|
||||
}
|
||||
local config = {
|
||||
snippets = { override_snippet },
|
||||
}
|
||||
|
||||
snippets.setup(config)
|
||||
|
||||
local cpp_snippets = mock_luasnip.added.cpp or {}
|
||||
local codeforces_count = 0
|
||||
for _, snippet in ipairs(cpp_snippets) do
|
||||
if snippet.trigger == 'cp.nvim/codeforces.cpp' then
|
||||
codeforces_count = codeforces_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
assert.equals(1, codeforces_count)
|
||||
end)
|
||||
|
||||
it('handles case-insensitive snippet triggers', function()
|
||||
local mixed_case_snippet = {
|
||||
trigger = 'cp.nvim/CodeForces.cpp',
|
||||
body = 'mixed case template',
|
||||
}
|
||||
local upper_case_snippet = {
|
||||
trigger = 'cp.nvim/ATCODER.cpp',
|
||||
body = 'upper case template',
|
||||
}
|
||||
local config = {
|
||||
snippets = { mixed_case_snippet, upper_case_snippet },
|
||||
}
|
||||
|
||||
snippets.setup(config)
|
||||
|
||||
local cpp_snippets = mock_luasnip.added.cpp or {}
|
||||
|
||||
local has_mixed_case = false
|
||||
local has_upper_case = false
|
||||
local default_codeforces_count = 0
|
||||
local default_atcoder_count = 0
|
||||
|
||||
for _, snippet in ipairs(cpp_snippets) do
|
||||
if snippet.trigger == 'cp.nvim/CodeForces.cpp' then
|
||||
has_mixed_case = true
|
||||
assert.equals('mixed case template', snippet.body)
|
||||
elseif snippet.trigger == 'cp.nvim/ATCODER.cpp' then
|
||||
has_upper_case = true
|
||||
assert.equals('upper case template', snippet.body)
|
||||
elseif snippet.trigger == 'cp.nvim/codeforces.cpp' then
|
||||
default_codeforces_count = default_codeforces_count + 1
|
||||
elseif snippet.trigger == 'cp.nvim/atcoder.cpp' then
|
||||
default_atcoder_count = default_atcoder_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
assert.is_true(has_mixed_case)
|
||||
assert.is_true(has_upper_case)
|
||||
assert.equals(0, default_codeforces_count, 'Default codeforces snippet should be overridden')
|
||||
assert.equals(0, default_atcoder_count, 'Default atcoder snippet should be overridden')
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
local M = {}
|
||||
|
||||
M.logged_messages = {}
|
||||
|
||||
local mock_logger = {
|
||||
log = function(msg, level)
|
||||
table.insert(M.logged_messages, { msg = msg, level = level })
|
||||
end,
|
||||
set_config = function() end,
|
||||
}
|
||||
|
||||
local function setup_vim_mocks()
|
||||
if not vim.fn then
|
||||
vim.fn = {}
|
||||
end
|
||||
vim.fn.expand = vim.fn.expand or function()
|
||||
return '/tmp/test.cpp'
|
||||
end
|
||||
vim.fn.mkdir = vim.fn.mkdir or function() end
|
||||
vim.fn.fnamemodify = vim.fn.fnamemodify or function(path)
|
||||
return path
|
||||
end
|
||||
vim.fn.tempname = vim.fn.tempname or function()
|
||||
return '/tmp/session'
|
||||
end
|
||||
if not vim.api then
|
||||
vim.api = {}
|
||||
end
|
||||
vim.api.nvim_get_current_buf = vim.api.nvim_get_current_buf or function()
|
||||
return 1
|
||||
end
|
||||
vim.api.nvim_buf_get_lines = vim.api.nvim_buf_get_lines or function()
|
||||
return { '' }
|
||||
end
|
||||
if not vim.cmd then
|
||||
vim.cmd = {}
|
||||
end
|
||||
vim.cmd = {
|
||||
only = function() end,
|
||||
e = function() end,
|
||||
split = function() end,
|
||||
vsplit = function() end,
|
||||
startinsert = function() end,
|
||||
stopinsert = function() end,
|
||||
}
|
||||
if not vim.system then
|
||||
vim.system = function(_)
|
||||
return {
|
||||
wait = function()
|
||||
return { code = 0 }
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
M.logged_messages = {}
|
||||
package.loaded['cp.log'] = mock_logger
|
||||
end
|
||||
|
||||
function M.setup_full()
|
||||
M.setup()
|
||||
setup_vim_mocks()
|
||||
|
||||
local cache = require('cp.cache')
|
||||
cache.load = function() end
|
||||
cache.set_test_cases = function() end
|
||||
cache.set_file_state = function() end
|
||||
cache.get_file_state = function()
|
||||
return nil
|
||||
end
|
||||
cache.get_contest_data = function()
|
||||
return nil
|
||||
end
|
||||
cache.get_test_cases = function()
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
function M.mock_scraper_success()
|
||||
package.loaded['cp.scrape'] = {
|
||||
scrape_problem = function()
|
||||
local state = require('cp.state')
|
||||
return {
|
||||
success = true,
|
||||
problem_id = state.get_problem_id(),
|
||||
test_cases = {
|
||||
{ input = '1 2', expected = '3' },
|
||||
{ input = '3 4', expected = '7' },
|
||||
},
|
||||
test_count = 2,
|
||||
}
|
||||
end,
|
||||
scrape_contest_metadata = function(_, _)
|
||||
return {
|
||||
success = true,
|
||||
problems = {
|
||||
{ id = 'a' },
|
||||
{ id = 'b' },
|
||||
{ id = 'c' },
|
||||
},
|
||||
}
|
||||
end,
|
||||
scrape_problems_parallel = function()
|
||||
return {}
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
function M.mock_async_scraper_success()
|
||||
package.loaded['cp.async.scraper'] = {
|
||||
scrape_contest_metadata_async = function(_, _, callback)
|
||||
vim.schedule(function()
|
||||
callback({
|
||||
success = true,
|
||||
problems = {
|
||||
{ id = 'a' },
|
||||
{ id = 'b' },
|
||||
{ id = 'c' },
|
||||
},
|
||||
})
|
||||
end)
|
||||
end,
|
||||
scrape_problem_async = function(_, _, problem_id, callback)
|
||||
vim.schedule(function()
|
||||
callback({
|
||||
success = true,
|
||||
problem_id = problem_id,
|
||||
test_cases = {
|
||||
{ input = '1 2', expected = '3' },
|
||||
{ input = '3 4', expected = '7' },
|
||||
},
|
||||
test_count = 2,
|
||||
timeout_ms = 2000,
|
||||
memory_mb = 256.0,
|
||||
url = 'https://example.com',
|
||||
})
|
||||
end)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
function M.mock_async_scraper_failure()
|
||||
package.loaded['cp.async.scraper'] = {
|
||||
scrape_contest_metadata_async = function(_, _, callback)
|
||||
vim.schedule(function()
|
||||
callback({
|
||||
success = false,
|
||||
error = 'mock network error',
|
||||
})
|
||||
end)
|
||||
end,
|
||||
scrape_problem_async = function(_, _, problem_id, callback)
|
||||
vim.schedule(function()
|
||||
callback({
|
||||
success = false,
|
||||
problem_id = problem_id,
|
||||
error = 'mock scraping failed',
|
||||
})
|
||||
end)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
function M.has_error_logged()
|
||||
for _, log_entry in ipairs(M.logged_messages) do
|
||||
if log_entry.level == vim.log.levels.ERROR then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function M.find_logged_message(pattern)
|
||||
for _, log_entry in ipairs(M.logged_messages) do
|
||||
if log_entry.msg and log_entry.msg:match(pattern) then
|
||||
return log_entry
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function M.fresh_require(module_name, additional_clears)
|
||||
additional_clears = additional_clears or {}
|
||||
|
||||
for _, clear_module in ipairs(additional_clears) do
|
||||
package.loaded[clear_module] = nil
|
||||
end
|
||||
package.loaded[module_name] = nil
|
||||
|
||||
return require(module_name)
|
||||
end
|
||||
|
||||
function M.teardown()
|
||||
package.loaded['cp.log'] = nil
|
||||
package.loaded['cp.scrape'] = nil
|
||||
package.loaded['cp.async.scraper'] = nil
|
||||
package.loaded['cp.async.jobs'] = nil
|
||||
package.loaded['cp.async.setup'] = nil
|
||||
package.loaded['cp.async'] = nil
|
||||
M.logged_messages = {}
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
describe('cp.telescope', function()
|
||||
local spec_helper = require('spec.spec_helper')
|
||||
|
||||
before_each(function()
|
||||
spec_helper.setup()
|
||||
|
||||
package.preload['telescope'] = function()
|
||||
return {
|
||||
register_extension = function(ext_config)
|
||||
return ext_config
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
package.preload['telescope.pickers'] = function()
|
||||
return {
|
||||
new = function(_, _)
|
||||
return {
|
||||
find = function() end,
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
package.preload['telescope.finders'] = function()
|
||||
return {
|
||||
new_table = function(opts)
|
||||
return opts
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
package.preload['telescope.config'] = function()
|
||||
return {
|
||||
values = {
|
||||
generic_sorter = function()
|
||||
return {}
|
||||
end,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
package.preload['telescope.actions'] = function()
|
||||
return {
|
||||
select_default = {
|
||||
replace = function() end,
|
||||
},
|
||||
close = function() end,
|
||||
}
|
||||
end
|
||||
|
||||
package.preload['telescope.actions.state'] = function()
|
||||
return {
|
||||
get_selected_entry = function()
|
||||
return nil
|
||||
end,
|
||||
}
|
||||
end
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
spec_helper.teardown()
|
||||
end)
|
||||
|
||||
describe('module loading', function()
|
||||
it('registers telescope extension without error', function()
|
||||
assert.has_no_errors(function()
|
||||
require('cp.pickers.telescope')
|
||||
end)
|
||||
end)
|
||||
|
||||
it('returns module with picker function', function()
|
||||
local telescope_cp = require('cp.pickers.telescope')
|
||||
assert.is_table(telescope_cp)
|
||||
assert.is_function(telescope_cp.pick)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('basic running', function()
|
||||
it('can run and open the picker with :CP pick', function()
|
||||
local cp = require('cp')
|
||||
assert.has_no_errors(function()
|
||||
cp.handle_command({ fargs = { 'pick' } })
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
Loading…
Add table
Add a link
Reference in a new issue