no more tests (for now

This commit is contained in:
Barrett Ruth 2025-10-02 10:34:14 -04:00
parent 00a1d57005
commit 27c265141e
16 changed files with 0 additions and 3381 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)