From 4361d2ae38b454e558d39a55caa4c23266832fb1 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 18 Sep 2025 22:48:55 -0400 Subject: [PATCH] feat: more test files --- spec/health_spec.lua | 227 +++++++++++++++++++++++++++++++++++++---- spec/problem_spec.lua | 139 +++++++++++++++++++++---- spec/snippets_spec.lua | 216 +++++++++++++++++++++++++++++++++++---- 3 files changed, 523 insertions(+), 59 deletions(-) diff --git a/spec/health_spec.lua b/spec/health_spec.lua index 3e1f059..42d0658 100644 --- a/spec/health_spec.lua +++ b/spec/health_spec.lua @@ -1,39 +1,226 @@ describe('cp.health', function() local health + local original_health = {} before_each(function() health = require('cp.health') + original_health.start = vim.health.start + original_health.ok = vim.health.ok + original_health.warn = vim.health.warn + original_health.error = vim.health.error + original_health.info = vim.health.info end) - describe('dependency checks', function() - it('checks for python availability', function() end) - - it('validates scraper dependencies', function() end) - - it('checks uv installation', function() end) + after_each(function() + vim.health = original_health end) - describe('scraper validation', function() - it('validates codeforces scraper', function() end) + describe('check function', function() + it('runs complete health check without error', function() + local health_calls = {} - it('validates atcoder scraper', function() end) + vim.health.start = function(msg) + table.insert(health_calls, { 'start', msg }) + end + vim.health.ok = function(msg) + table.insert(health_calls, { 'ok', msg }) + end + vim.health.warn = function(msg) + table.insert(health_calls, { 'warn', msg }) + end + vim.health.error = function(msg) + table.insert(health_calls, { 'error', msg }) + end + vim.health.info = function(msg) + table.insert(health_calls, { 'info', msg }) + end - it('validates cses scraper', function() end) - end) + assert.has_no_errors(function() + health.check() + end) - describe('configuration validation', function() - it('checks config file validity', function() end) + assert.is_true(#health_calls > 0) + assert.equals('start', health_calls[1][1]) + assert.equals('cp.nvim health check', health_calls[1][2]) + end) - it('validates language configurations', function() end) + it('reports version information', function() + local info_messages = {} + vim.health.start = function() end + vim.health.ok = function() end + vim.health.warn = function() end + vim.health.error = function() end + vim.health.info = function(msg) + table.insert(info_messages, msg) + end - it('checks snippet configurations', function() end) - end) + health.check() - describe('system checks', function() - it('checks file permissions', function() end) + local version_reported = false + for _, msg in ipairs(info_messages) do + if msg:match('^Version:') then + version_reported = true + break + end + end + assert.is_true(version_reported) + end) - it('validates cache directory access', function() end) + it('checks neovim version compatibility', function() + local messages = {} + vim.health.start = function() end + vim.health.ok = function(msg) + table.insert(messages, { 'ok', msg }) + end + vim.health.error = function(msg) + table.insert(messages, { 'error', msg }) + end + vim.health.warn = function() end + vim.health.info = function() end - it('checks network connectivity', function() end) + health.check() + + local nvim_check_found = false + for _, msg in ipairs(messages) do + if msg[2]:match('Neovim') then + nvim_check_found = true + if vim.fn.has('nvim-0.10.0') == 1 then + assert.equals('ok', msg[1]) + assert.is_true(msg[2]:match('detected')) + else + assert.equals('error', msg[1]) + assert.is_true(msg[2]:match('requires')) + end + break + end + end + assert.is_true(nvim_check_found) + end) + + it('checks uv executable availability', function() + local messages = {} + vim.health.start = function() end + vim.health.ok = function(msg) + table.insert(messages, { 'ok', msg }) + end + vim.health.warn = function(msg) + table.insert(messages, { 'warn', msg }) + end + vim.health.error = function() end + vim.health.info = function() end + + health.check() + + local uv_check_found = false + for _, msg in ipairs(messages) do + if msg[2]:match('uv') then + uv_check_found = true + if vim.fn.executable('uv') == 1 then + assert.equals('ok', msg[1]) + assert.is_true(msg[2]:match('found')) + else + assert.equals('warn', msg[1]) + assert.is_true(msg[2]:match('not found')) + end + break + end + end + assert.is_true(uv_check_found) + end) + + it('validates scraper files exist', function() + local messages = {} + vim.health.start = function() end + vim.health.ok = function(msg) + table.insert(messages, { 'ok', msg }) + end + vim.health.error = function(msg) + table.insert(messages, { 'error', msg }) + end + vim.health.warn = function() end + vim.health.info = function() end + + health.check() + + local scrapers = { 'atcoder.py', 'codeforces.py', 'cses.py' } + for _, scraper in ipairs(scrapers) do + local found = false + for _, msg in ipairs(messages) do + if msg[2]:match(scraper) then + found = true + break + end + end + assert.is_true(found, 'Expected health check for ' .. scraper) + end + end) + + it('reports luasnip availability', function() + local info_messages = {} + vim.health.start = function() end + vim.health.ok = function(msg) + table.insert(info_messages, msg) + end + vim.health.warn = function() end + vim.health.error = function() end + vim.health.info = function(msg) + table.insert(info_messages, msg) + end + + health.check() + + local luasnip_reported = false + for _, msg in ipairs(info_messages) do + if msg:match('LuaSnip') then + luasnip_reported = true + break + end + end + assert.is_true(luasnip_reported) + end) + + it('reports current context information', function() + local info_messages = {} + vim.health.start = function() end + vim.health.ok = function() end + vim.health.warn = function() end + vim.health.error = function() end + vim.health.info = function(msg) + table.insert(info_messages, msg) + end + + health.check() + + local context_reported = false + for _, msg in ipairs(info_messages) do + if msg:match('context') then + context_reported = true + break + end + end + assert.is_true(context_reported) + end) + + it('indicates plugin readiness', function() + local ok_messages = {} + vim.health.start = function() end + vim.health.ok = function(msg) + table.insert(ok_messages, msg) + end + vim.health.warn = function() end + vim.health.error = function() end + vim.health.info = function() end + + health.check() + + local ready_reported = false + for _, msg in ipairs(ok_messages) do + if msg:match('ready') then + ready_reported = true + break + end + end + assert.is_true(ready_reported) + end) end) end) diff --git a/spec/problem_spec.lua b/spec/problem_spec.lua index dfbf00f..adefb89 100644 --- a/spec/problem_spec.lua +++ b/spec/problem_spec.lua @@ -5,35 +5,136 @@ describe('cp.problem', function() problem = require('cp.problem') end) - describe('problem creation', function() - it('creates problem files with correct naming', function() end) + describe('create_context', function() + local base_config = { + contests = { + atcoder = { + default_language = 'cpp', + cpp = { extension = 'cpp' }, + python = { extension = 'py' }, + }, + codeforces = { + default_language = 'cpp', + cpp = { extension = 'cpp' }, + }, + }, + } - it('applies language-specific templates', function() end) + it('creates basic context with required fields', function() + local context = problem.create_context('atcoder', 'abc123', 'a', base_config) - it('sets up directory structure correctly', function() end) - end) + assert.equals('atcoder', context.contest) + assert.equals('abc123', context.contest_id) + assert.equals('a', context.problem_id) + assert.equals('abc123a', context.problem_name) + assert.equals('abc123a.cpp', context.source_file) + assert.equals('build/abc123a.run', context.binary_file) + assert.equals('io/abc123a.cpin', context.input_file) + assert.equals('io/abc123a.cpout', context.output_file) + assert.equals('io/abc123a.expected', context.expected_file) + end) - describe('problem metadata', function() - it('extracts problem information correctly', function() end) + it('handles context without problem_id', function() + local context = problem.create_context('codeforces', '1933', nil, base_config) - it('handles missing metadata gracefully', function() end) + assert.equals('codeforces', context.contest) + assert.equals('1933', context.contest_id) + assert.is_nil(context.problem_id) + assert.equals('1933', context.problem_name) + assert.equals('1933.cpp', context.source_file) + assert.equals('build/1933.run', context.binary_file) + end) - it('validates problem identifiers', function() end) - end) + it('uses default language from contest config', function() + local context = problem.create_context('atcoder', 'abc123', 'a', base_config) + assert.equals('abc123a.cpp', context.source_file) + end) - describe('file management', function() - it('creates solution files in correct locations', function() end) + it('respects explicit language parameter', function() + local context = problem.create_context('atcoder', 'abc123', 'a', base_config, 'python') + assert.equals('abc123a.py', context.source_file) + end) - it('handles existing files appropriately', function() end) + it('uses custom filename function when provided', function() + local config_with_custom = vim.tbl_deep_extend('force', base_config, { + filename = function(contest, contest_id, problem_id) + return contest .. '_' .. contest_id .. (problem_id and ('_' .. problem_id) or '') + end, + }) - it('manages backup files correctly', function() end) - end) + local context = problem.create_context('atcoder', 'abc123', 'a', config_with_custom) + assert.equals('atcoder_abc123_a.cpp', context.source_file) + assert.equals('atcoder_abc123_a', context.problem_name) + end) - describe('buffer setup', function() - it('opens problem files in appropriate buffers', function() end) + it('validates required parameters', function() + assert.has_error(function() + problem.create_context(nil, 'abc123', 'a', base_config) + end) - it('sets correct buffer options', function() end) + assert.has_error(function() + problem.create_context('atcoder', nil, 'a', base_config) + end) - it('applies filetype-specific settings', function() end) + assert.has_error(function() + problem.create_context('atcoder', 'abc123', 'a', nil) + end) + end) + + it('validates contest exists in config', function() + assert.has_error(function() + problem.create_context('invalid_contest', 'abc123', 'a', base_config) + end) + end) + + it('validates language exists in contest config', function() + assert.has_error(function() + problem.create_context('atcoder', 'abc123', 'a', base_config, 'invalid_language') + end) + end) + + it('validates default language exists', function() + local bad_config = { + contests = { + test_contest = { + default_language = 'nonexistent', + }, + }, + } + + assert.has_error(function() + problem.create_context('test_contest', 'abc123', 'a', bad_config) + end) + end) + + it('validates language extension is configured', function() + local bad_config = { + contests = { + test_contest = { + default_language = 'cpp', + cpp = {}, + }, + }, + } + + assert.has_error(function() + problem.create_context('test_contest', 'abc123', 'a', bad_config) + end) + end) + + it('handles complex contest and problem ids', function() + local context = problem.create_context('atcoder', 'arc123', 'f', base_config) + assert.equals('arc123f', context.problem_name) + assert.equals('arc123f.cpp', context.source_file) + assert.equals('build/arc123f.run', context.binary_file) + end) + + it('generates correct io file paths', function() + local context = problem.create_context('atcoder', 'abc123', 'a', base_config) + + assert.equals('io/abc123a.cpin', context.input_file) + assert.equals('io/abc123a.cpout', context.output_file) + assert.equals('io/abc123a.expected', context.expected_file) + end) end) end) diff --git a/spec/snippets_spec.lua b/spec/snippets_spec.lua index e604c7e..c2b5f0a 100644 --- a/spec/snippets_spec.lua +++ b/spec/snippets_spec.lua @@ -1,39 +1,215 @@ describe('cp.snippets', function() local snippets + local mock_luasnip before_each(function() snippets = 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) - describe('snippet loading', function() - it('loads default snippets correctly', function() end) - - it('loads user snippets from config', function() end) - - it('handles missing snippet files gracefully', function() end) + after_each(function() + package.loaded['luasnip'] = nil + package.loaded['luasnip.extras.fmt'] = nil end) - describe('snippet expansion', function() - it('expands basic templates correctly', function() end) + describe('setup without luasnip', function() + it('handles missing luasnip gracefully', function() + package.loaded['luasnip'] = nil - it('handles language-specific snippets', function() end) - - it('processes snippet placeholders', function() end) + assert.has_no_errors(function() + snippets.setup({}) + end) + end) end) - describe('template generation', function() - it('generates cpp templates', function() end) + describe('setup with luasnip available', function() + it('sets up default cpp snippets for all contests', function() + local config = { snippets = {} } - it('generates python templates', function() end) + snippets.setup(config) - it('applies contest-specific templates', function() end) - end) + assert.is_not_nil(mock_luasnip.added.cpp) + assert.is_true(#mock_luasnip.added.cpp >= 3) - describe('buffer integration', function() - it('inserts snippets into current buffer', function() end) + local triggers = {} + for _, snippet in ipairs(mock_luasnip.added.cpp) do + table.insert(triggers, snippet.trigger) + end - it('positions cursor correctly after expansion', function() 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('handles multiple snippet insertions', function() 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_true(codeforces_snippet.body.template:match('#include')) + assert.is_true(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 nil config gracefully', function() + assert.has_no_errors(function() + snippets.setup() + 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) end) end)