From 27c265141ecccee3bea1c99d7e67d91b5b2a363c Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 2 Oct 2025 10:34:14 -0400 Subject: [PATCH] no more tests (for now --- spec/ansi_spec.lua | 243 ----------- spec/cache_spec.lua | 205 --------- spec/command_parsing_spec.lua | 729 --------------------------------- spec/config_spec.lua | 334 --------------- spec/diff_spec.lua | 91 ---- spec/error_boundaries_spec.lua | 221 ---------- spec/execute_spec.lua | 429 ------------------- spec/fzf_lua_spec.lua | 40 -- spec/highlight_spec.lua | 107 ----- spec/panel_spec.lua | 109 ----- spec/picker_spec.lua | 93 ----- spec/run_render_spec.lua | 200 --------- spec/run_spec.lua | 27 -- spec/snippets_spec.lua | 261 ------------ spec/spec_helper.lua | 205 --------- spec/telescope_spec.lua | 87 ---- 16 files changed, 3381 deletions(-) delete mode 100644 spec/ansi_spec.lua delete mode 100644 spec/cache_spec.lua delete mode 100644 spec/command_parsing_spec.lua delete mode 100644 spec/config_spec.lua delete mode 100644 spec/diff_spec.lua delete mode 100644 spec/error_boundaries_spec.lua delete mode 100644 spec/execute_spec.lua delete mode 100644 spec/fzf_lua_spec.lua delete mode 100644 spec/highlight_spec.lua delete mode 100644 spec/panel_spec.lua delete mode 100644 spec/picker_spec.lua delete mode 100644 spec/run_render_spec.lua delete mode 100644 spec/run_spec.lua delete mode 100644 spec/snippets_spec.lua delete mode 100644 spec/spec_helper.lua delete mode 100644 spec/telescope_spec.lua diff --git a/spec/ansi_spec.lua b/spec/ansi_spec.lua deleted file mode 100644 index af62c36..0000000 --- a/spec/ansi_spec.lua +++ /dev/null @@ -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) diff --git a/spec/cache_spec.lua b/spec/cache_spec.lua deleted file mode 100644 index 50f74a3..0000000 --- a/spec/cache_spec.lua +++ /dev/null @@ -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) diff --git a/spec/command_parsing_spec.lua b/spec/command_parsing_spec.lua deleted file mode 100644 index d5a07d3..0000000 --- a/spec/command_parsing_spec.lua +++ /dev/null @@ -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) diff --git a/spec/config_spec.lua b/spec/config_spec.lua deleted file mode 100644 index 21723b1..0000000 --- a/spec/config_spec.lua +++ /dev/null @@ -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) diff --git a/spec/diff_spec.lua b/spec/diff_spec.lua deleted file mode 100644 index 31fa395..0000000 --- a/spec/diff_spec.lua +++ /dev/null @@ -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) diff --git a/spec/error_boundaries_spec.lua b/spec/error_boundaries_spec.lua deleted file mode 100644 index 7d88a0d..0000000 --- a/spec/error_boundaries_spec.lua +++ /dev/null @@ -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) diff --git a/spec/execute_spec.lua b/spec/execute_spec.lua deleted file mode 100644 index e221bd4..0000000 --- a/spec/execute_spec.lua +++ /dev/null @@ -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) diff --git a/spec/fzf_lua_spec.lua b/spec/fzf_lua_spec.lua deleted file mode 100644 index a134fcb..0000000 --- a/spec/fzf_lua_spec.lua +++ /dev/null @@ -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) diff --git a/spec/highlight_spec.lua b/spec/highlight_spec.lua deleted file mode 100644 index 8897cc4..0000000 --- a/spec/highlight_spec.lua +++ /dev/null @@ -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) diff --git a/spec/panel_spec.lua b/spec/panel_spec.lua deleted file mode 100644 index b5fdfe2..0000000 --- a/spec/panel_spec.lua +++ /dev/null @@ -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) diff --git a/spec/picker_spec.lua b/spec/picker_spec.lua deleted file mode 100644 index 2af76f7..0000000 --- a/spec/picker_spec.lua +++ /dev/null @@ -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) diff --git a/spec/run_render_spec.lua b/spec/run_render_spec.lua deleted file mode 100644 index 72f58c4..0000000 --- a/spec/run_render_spec.lua +++ /dev/null @@ -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) diff --git a/spec/run_spec.lua b/spec/run_spec.lua deleted file mode 100644 index f7eb772..0000000 --- a/spec/run_spec.lua +++ /dev/null @@ -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) diff --git a/spec/snippets_spec.lua b/spec/snippets_spec.lua deleted file mode 100644 index 944e0d9..0000000 --- a/spec/snippets_spec.lua +++ /dev/null @@ -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) diff --git a/spec/spec_helper.lua b/spec/spec_helper.lua deleted file mode 100644 index acbdf62..0000000 --- a/spec/spec_helper.lua +++ /dev/null @@ -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 diff --git a/spec/telescope_spec.lua b/spec/telescope_spec.lua deleted file mode 100644 index bc4876a..0000000 --- a/spec/telescope_spec.lua +++ /dev/null @@ -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)