From a33e66680b6673530be5586a80ec88fad2ef6be3 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Sun, 21 Sep 2025 11:10:54 -0400 Subject: [PATCH] feat(picker): picker support --- doc/cp.txt | 41 +++++++- lua/cp/config.lua | 10 ++ lua/cp/constants.lua | 8 +- lua/cp/init.lua | 44 ++++++++ lua/cp/pickers/fzf_lua.lua | 123 +++++++++++++++++++++++ lua/cp/pickers/init.lua | 145 +++++++++++++++++++++++++++ lua/cp/pickers/telescope.lua | 130 ++++++++++++++++++++++++ spec/config_spec.lua | 33 ++++++ spec/fzf_lua_spec.lua | 31 ++++++ spec/picker_spec.lua | 188 +++++++++++++++++++++++++++++++++++ spec/telescope_spec.lua | 78 +++++++++++++++ 11 files changed, 829 insertions(+), 2 deletions(-) create mode 100644 lua/cp/pickers/fzf_lua.lua create mode 100644 lua/cp/pickers/init.lua create mode 100644 lua/cp/pickers/telescope.lua create mode 100644 spec/fzf_lua_spec.lua create mode 100644 spec/picker_spec.lua create mode 100644 spec/telescope_spec.lua diff --git a/doc/cp.txt b/doc/cp.txt index 1ad6d93..c2c2964 100644 --- a/doc/cp.txt +++ b/doc/cp.txt @@ -72,6 +72,12 @@ COMMANDS *cp-commands* Use --debug flag to compile with debug flags. Requires contest setup first. + :CP pick Launch configured picker for interactive + platform/contest/problem selection. Requires + picker = 'telescope' or picker = 'fzf-lua' + in configuration and corresponding plugin + to be installed. + Navigation Commands ~ :CP next Navigate to next problem in current contest. Stops at last problem (no wrapping). @@ -117,6 +123,32 @@ Template Variables ~ g++ abc324a.cpp -o build/abc324a.run -std=c++17 < +============================================================================== +PICKER INTEGRATION *cp-picker* + +When picker integration is enabled in configuration, cp.nvim provides interactive +platform, contest, and problem selection using telescope.nvim or fzf-lua. + +:CP pick *:CP-pick* + Launch configured picker for interactive problem selection. + Flow: Platform → Contest → Problem → Setup + + Requires picker = 'telescope' or picker = 'fzf-lua' in configuration. + Requires corresponding plugin (telescope.nvim or fzf-lua) to be installed. + Picker availability is checked at runtime when command is executed. + +Picker Flow ~ + 1. Platform Selection: Choose from AtCoder, Codeforces, CSES + 2. Contest Selection: Choose from available contests for selected platform + 3. Problem Selection: Choose from problems in selected contest + 4. Problem Setup: Automatically runs equivalent of :CP platform contest problem + +Notes ~ + • Contest lists are fetched dynamically using scrapers + • Large contest lists may take time to load + • Runtime picker validation - shows clear error if picker plugin not available + • Picker configuration can be changed without plugin restart + ============================================================================== CONFIGURATION *cp-config* @@ -131,10 +163,11 @@ Here's an example configuration with lazy.nvim: >lua default = { cpp = { compile = { 'g++', '{source}', '-o', '{binary}', - '-std=c++17' }, + '-std=c++17', '-fdiagnostic-colors=always' }, test = { '{binary}' }, debug = { 'g++', '{source}', '-o', '{binary}', '-std=c++17', '-g', + '-fdiagnostic-colors=always' '-fsanitize=address,undefined' }, }, python = { @@ -165,6 +198,7 @@ Here's an example configuration with lazy.nvim: >lua '--word-diff-regex=.', '--no-prefix' }, }, }, + picker = 'telescope', -- 'telescope', 'fzf-lua', or nil (disabled) } } < @@ -180,6 +214,11 @@ Here's an example configuration with lazy.nvim: >lua Default: all scrapers enabled {run_panel} (|RunPanelConfig|) Test panel behavior configuration. {diff} (|DiffConfig|) Diff backend configuration. + {picker} (string, optional) Picker integration: "telescope", + "fzf-lua", or nil to disable. When enabled, provides + :Telescope cp or :FzfLua cp commands for interactive + platform/contest/problem selection. Requires the + corresponding picker plugin to be installed. {filename} (function, optional) Custom filename generation. function(contest, contest_id, problem_id, config, language) Should return full filename with extension. diff --git a/lua/cp/config.lua b/lua/cp/config.lua index a1564fd..838107b 100644 --- a/lua/cp/config.lua +++ b/lua/cp/config.lua @@ -52,6 +52,7 @@ ---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string ---@field run_panel RunPanelConfig ---@field diff DiffConfig +---@field picker "telescope"|"fzf-lua"|nil ---@class cp.UserConfig ---@field contests? table @@ -62,6 +63,7 @@ ---@field filename? fun(contest: string, contest_id: string, problem_id?: string, config: cp.Config, language?: string): string ---@field run_panel? RunPanelConfig ---@field diff? DiffConfig +---@field picker? "telescope"|"fzf-lua"|nil local M = {} local constants = require('cp.constants') @@ -110,6 +112,7 @@ M.defaults = { args = { 'diff', '--no-index', '--word-diff=plain', '--word-diff-regex=.', '--no-prefix' }, }, }, + picker = nil, } ---@param user_config cp.UserConfig|nil @@ -129,6 +132,7 @@ function M.setup(user_config) filename = { user_config.filename, { 'function', 'nil' }, true }, run_panel = { user_config.run_panel, { 'table', 'nil' }, true }, diff = { user_config.diff, { 'table', 'nil' }, true }, + picker = { user_config.picker, { 'string', 'nil' }, true }, }) if user_config.contests then @@ -164,6 +168,12 @@ function M.setup(user_config) end end end + + if user_config.picker then + if not vim.tbl_contains({ 'telescope', 'fzf-lua' }, user_config.picker) then + error(("Invalid picker '%s'. Must be 'telescope' or 'fzf-lua'"):format(user_config.picker)) + end + end end local config = vim.tbl_deep_extend('force', M.defaults, user_config or {}) diff --git a/lua/cp/constants.lua b/lua/cp/constants.lua index 13cb27d..c14569b 100644 --- a/lua/cp/constants.lua +++ b/lua/cp/constants.lua @@ -1,7 +1,13 @@ local M = {} M.PLATFORMS = { 'atcoder', 'codeforces', 'cses' } -M.ACTIONS = { 'run', 'next', 'prev' } +M.ACTIONS = { 'run', 'next', 'prev', 'pick' } + +M.PLATFORM_DISPLAY_NAMES = { + atcoder = 'AtCoder', + codeforces = 'CodeForces', + cses = 'CSES', +} M.CPP = 'cpp' M.PYTHON = 'python' diff --git a/lua/cp/init.lua b/lua/cp/init.lua index 36c2faf..7d82cc6 100644 --- a/lua/cp/init.lua +++ b/lua/cp/init.lua @@ -698,6 +698,48 @@ local function navigate_problem(delta, language) setup_problem(state.contest_id, new_problem.id, language) end +local function handle_pick_action() + if not config.picker then + logger.log( + 'No picker configured. Set picker = "telescope" or picker = "fzf-lua" in config', + vim.log.levels.ERROR + ) + return + end + + if config.picker == 'telescope' then + local ok, telescope = pcall(require, 'telescope') + if not ok then + logger.log( + 'Telescope not available. Install telescope.nvim or change picker config', + vim.log.levels.ERROR + ) + return + end + local ok_cp, telescope_cp = pcall(require, 'cp.pickers.telescope') + if not ok_cp then + logger.log('Failed to load telescope integration', vim.log.levels.ERROR) + return + end + telescope_cp.platform_picker() + elseif config.picker == 'fzf-lua' then + local ok, _ = pcall(require, 'fzf-lua') + if not ok then + logger.log( + 'fzf-lua not available. Install fzf-lua or change picker config', + vim.log.levels.ERROR + ) + return + end + local ok_cp, fzf_cp = pcall(require, 'cp.pickers.fzf_lua') + if not ok_cp then + logger.log('Failed to load fzf-lua integration', vim.log.levels.ERROR) + return + end + fzf_cp.platform_picker() + end +end + local function restore_from_current_file() local current_file = vim.fn.expand('%:p') if current_file == '' then @@ -837,6 +879,8 @@ function M.handle_command(opts) navigate_problem(1, cmd.language) elseif cmd.action == 'prev' then navigate_problem(-1, cmd.language) + elseif cmd.action == 'pick' then + handle_pick_action() end return end diff --git a/lua/cp/pickers/fzf_lua.lua b/lua/cp/pickers/fzf_lua.lua new file mode 100644 index 0000000..eaec2e7 --- /dev/null +++ b/lua/cp/pickers/fzf_lua.lua @@ -0,0 +1,123 @@ +local picker_utils = require('cp.pickers') + +local function problem_picker(platform, contest_id) + local constants = require('cp.constants') + local platform_display_name = constants.PLATFORM_DISPLAY_NAMES[platform] or platform + local fzf = require('fzf-lua') + local problems = picker_utils.get_problems_for_contest(platform, contest_id) + + if #problems == 0 then + vim.notify( + ('No problems found for contest: %s %s'):format(platform_display_name, contest_id), + vim.log.levels.WARN + ) + return + end + + local entries = vim.tbl_map(function(problem) + return problem.display_name + end, problems) + + return fzf.fzf_exec(entries, { + prompt = ('Select Problem (%s %s)> '):format(platform_display_name, contest_id), + actions = { + ['default'] = function(selected) + if not selected or #selected == 0 then + return + end + + local selected_name = selected[1] + local problem = nil + for _, p in ipairs(problems) do + if p.display_name == selected_name then + problem = p + break + end + end + + if problem then + picker_utils.setup_problem(platform, contest_id, problem.id) + end + end, + }, + }) +end + +local function contest_picker(platform) + local constants = require('cp.constants') + local platform_display_name = constants.PLATFORM_DISPLAY_NAMES[platform] or platform + local fzf = require('fzf-lua') + local contests = picker_utils.get_contests_for_platform(platform) + + if #contests == 0 then + vim.notify( + ('No contests found for platform: %s'):format(platform_display_name), + vim.log.levels.WARN + ) + return + end + + local entries = vim.tbl_map(function(contest) + return contest.display_name + end, contests) + + return fzf.fzf_exec(entries, { + prompt = ('Select Contest (%s)> '):format(platform_display_name), + actions = { + ['default'] = function(selected) + if not selected or #selected == 0 then + return + end + + local selected_name = selected[1] + local contest = nil + for _, c in ipairs(contests) do + if c.display_name == selected_name then + contest = c + break + end + end + + if contest then + problem_picker(platform, contest.id) + end + end, + }, + }) +end + +local function platform_picker() + local fzf = require('fzf-lua') + local platforms = picker_utils.get_platforms() + local entries = vim.tbl_map(function(platform) + return platform.display_name + end, platforms) + + return fzf.fzf_exec(entries, { + prompt = 'Select Platform> ', + actions = { + ['default'] = function(selected) + if not selected or #selected == 0 then + return + end + + local selected_name = selected[1] + local platform = nil + for _, p in ipairs(platforms) do + if p.display_name == selected_name then + platform = p + break + end + end + + if platform then + contest_picker(platform.id) + end + end, + }, + }) +end + +return { + platform_picker = platform_picker, +} diff --git a/lua/cp/pickers/init.lua b/lua/cp/pickers/init.lua new file mode 100644 index 0000000..a90cd8d --- /dev/null +++ b/lua/cp/pickers/init.lua @@ -0,0 +1,145 @@ +local M = {} + +local cache = require('cp.cache') +local logger = require('cp.log') +local scrape = require('cp.scrape') + +---@class cp.PlatformItem +---@field id string Platform identifier (e.g. "codeforces", "atcoder", "cses") +---@field display_name string Human-readable platform name (e.g. "Codeforces", "AtCoder", "CSES") + +---@class cp.ContestItem +---@field id string Contest identifier (e.g. "1951", "abc324", "sorting") +---@field name string Full contest name (e.g. "Educational Codeforces Round 168") +---@field display_name string Formatted display name for picker + +---@class cp.ProblemItem +---@field id string Problem identifier (e.g. "a", "b", "c") +---@field name string Problem name (e.g. "Two Permutations", "Painting Walls") +---@field display_name string Formatted display name for picker + +---Get list of available competitive programming platforms +---@return cp.PlatformItem[] +local function get_platforms() + local constants = require('cp.constants') + return vim.tbl_map(function(platform) + return { + id = platform, + display_name = constants.PLATFORM_DISPLAY_NAMES[platform] or platform, + } + end, constants.PLATFORMS) +end + +---Get list of contests for a specific platform +---@param platform string Platform identifier (e.g. "codeforces", "atcoder") +---@return cp.ContestItem[] +local function get_contests_for_platform(platform) + local contests = {} + + local function get_plugin_path() + local plugin_path = debug.getinfo(1, 'S').source:sub(2) + return vim.fn.fnamemodify(plugin_path, ':h:h:h') + end + + local plugin_path = get_plugin_path() + local cmd = { + 'uv', + 'run', + '--directory', + plugin_path, + '-m', + 'scrapers.' .. platform, + 'contests', + } + local result = vim + .system(cmd, { + cwd = plugin_path, + text = true, + timeout = 30000, + }) + :wait() + + if result.code ~= 0 then + logger.log( + ('Failed to get contests for %s: %s'):format(platform, result.stderr or 'unknown error'), + vim.log.levels.ERROR + ) + return contests + end + + local ok, data = pcall(vim.json.decode, result.stdout) + if not ok or not data.success then + logger.log(('Failed to parse contest data for %s'):format(platform), vim.log.levels.ERROR) + return contests + end + + for _, contest in ipairs(data.contests or {}) do + table.insert(contests, { + id = contest.id, + name = contest.name, + display_name = contest.display_name, + }) + end + + return contests +end + +---Get list of problems for a specific contest +---@param platform string Platform identifier +---@param contest_id string Contest identifier +---@return cp.ProblemItem[] +local function get_problems_for_contest(platform, contest_id) + local problems = {} + + cache.load() + local contest_data = cache.get_contest_data(platform, contest_id) + if contest_data and contest_data.problems then + for _, problem in ipairs(contest_data.problems) do + table.insert(problems, { + id = problem.id, + name = problem.name, + display_name = problem.name, + }) + end + return problems + end + + local metadata_result = scrape.scrape_contest_metadata(platform, contest_id) + if not metadata_result.success then + logger.log( + ('Failed to get problems for %s %s: %s'):format( + platform, + contest_id, + metadata_result.error or 'unknown error' + ), + vim.log.levels.ERROR + ) + return problems + end + + for _, problem in ipairs(metadata_result.problems or {}) do + table.insert(problems, { + id = problem.id, + name = problem.name, + display_name = problem.name, + }) + end + + return problems +end + +---Set up a specific problem by calling the main CP handler +---@param platform string Platform identifier +---@param contest_id string Contest identifier +---@param problem_id string Problem identifier +local function setup_problem(platform, contest_id, problem_id) + local cp = require('cp') + cp.handle_command({ fargs = { platform, contest_id, problem_id } }) +end + +M.get_platforms = get_platforms +M.get_contests_for_platform = get_contests_for_platform +M.get_problems_for_contest = get_problems_for_contest +M.setup_problem = setup_problem + +return M diff --git a/lua/cp/pickers/telescope.lua b/lua/cp/pickers/telescope.lua new file mode 100644 index 0000000..604bbf8 --- /dev/null +++ b/lua/cp/pickers/telescope.lua @@ -0,0 +1,130 @@ +local finders = require('telescope.finders') +local pickers = require('telescope.pickers') +local telescope = require('telescope') +local conf = require('telescope.config').values +local action_state = require('telescope.actions.state') +local actions = require('telescope.actions') + +local picker_utils = require('cp.pickers') + +local function platform_picker(opts) + opts = opts or {} + + local platforms = picker_utils.get_platforms() + + pickers + .new(opts, { + prompt_title = 'Select Platform', + finder = finders.new_table({ + results = platforms, + entry_maker = function(entry) + return { + value = entry, + display = entry.display_name, + ordinal = entry.display_name, + } + end, + }), + sorter = conf.generic_sorter(opts), + attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + actions.close(prompt_bufnr) + + if selection then + contest_picker(opts, selection.value.id) + end + end) + return true + end, + }) + :find() +end + +local function contest_picker(opts, platform) + local constants = require('cp.constants') + local platform_display_name = constants.PLATFORM_DISPLAY_NAMES[platform] or platform + local contests = picker_utils.get_contests_for_platform(platform) + + if #contests == 0 then + vim.notify( + ('No contests found for platform: %s'):format(platform_display_name), + vim.log.levels.WARN + ) + return + end + + pickers + .new(opts, { + prompt_title = ('Select Contest (%s)'):format(platform_display_name), + finder = finders.new_table({ + results = contests, + entry_maker = function(entry) + return { + value = entry, + display = entry.display_name, + ordinal = entry.display_name, + } + end, + }), + sorter = conf.generic_sorter(opts), + attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + actions.close(prompt_bufnr) + + if selection then + problem_picker(opts, platform, selection.value.id) + end + end) + return true + end, + }) + :find() +end + +local function problem_picker(opts, platform, contest_id) + local constants = require('cp.constants') + local platform_display_name = constants.PLATFORM_DISPLAY_NAMES[platform] or platform + local problems = picker_utils.get_problems_for_contest(platform, contest_id) + + if #problems == 0 then + vim.notify( + ('No problems found for contest: %s %s'):format(platform_display_name, contest_id), + vim.log.levels.WARN + ) + return + end + + pickers + .new(opts, { + prompt_title = ('Select Problem (%s %s)'):format(platform_display_name, contest_id), + finder = finders.new_table({ + results = problems, + entry_maker = function(entry) + return { + value = entry, + display = entry.display_name, + ordinal = entry.display_name, + } + end, + }), + sorter = conf.generic_sorter(opts), + attach_mappings = function(prompt_bufnr, map) + actions.select_default:replace(function() + local selection = action_state.get_selected_entry() + actions.close(prompt_bufnr) + + if selection then + picker_utils.setup_problem(platform, contest_id, selection.value.id) + end + end) + return true + end, + }) + :find() +end + +return { + platform_picker = platform_picker, +} diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 8c4c79c..f3f3738 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -231,6 +231,39 @@ describe('cp.config', function() 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() diff --git a/spec/fzf_lua_spec.lua b/spec/fzf_lua_spec.lua new file mode 100644 index 0000000..f22e35e --- /dev/null +++ b/spec/fzf_lua_spec.lua @@ -0,0 +1,31 @@ +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(entries, opts) 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 platform_picker function', function() + local fzf_lua_cp = require('cp.pickers.fzf_lua') + assert.is_table(fzf_lua_cp) + assert.is_function(fzf_lua_cp.platform_picker) + end) + end) +end) diff --git a/spec/picker_spec.lua b/spec/picker_spec.lua new file mode 100644 index 0000000..daac53f --- /dev/null +++ b/spec/picker_spec.lua @@ -0,0 +1,188 @@ +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_platforms', function() + it('returns platform list with display names', function() + local platforms = picker.get_platforms() + + assert.is_table(platforms) + assert.is_true(#platforms > 0) + + for _, platform in ipairs(platforms) do + assert.is_string(platform.id) + assert.is_string(platform.display_name) + assert.is_true(platform.display_name:match('^%u')) + end + end) + + it('includes expected platforms with correct display names', function() + local platforms = picker.get_platforms() + local platform_map = {} + for _, p in ipairs(platforms) do + platform_map[p.id] = p.display_name + end + + assert.equals('CodeForces', platform_map['codeforces']) + assert.equals('AtCoder', platform_map['atcoder']) + assert.equals('CSES', platform_map['cses']) + end) + end) + + describe('get_contests_for_platform', function() + it('returns empty list when scraper fails', function() + vim.system = function(cmd, opts) + 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(cmd, opts) + 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() + vim.system = function(cmd, opts) + 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) + + describe('get_problems_for_contest', function() + it('returns problems from cache when available', function() + local cache = require('cp.cache') + cache.load = function() end + cache.get_contest_data = function(platform, contest_id) + return { + problems = { + { id = 'a', name = 'Problem A' }, + { id = 'b', name = 'Problem B' }, + }, + } + end + + local problems = picker.get_problems_for_contest('test_platform', 'test_contest') + assert.is_table(problems) + assert.equals(2, #problems) + assert.equals('a', problems[1].id) + assert.equals('Problem A', problems[1].name) + assert.equals('a - Problem A', problems[1].display_name) + end) + + it('falls back to scraping when cache miss', function() + local cache = require('cp.cache') + local scrape = require('cp.scrape') + + cache.load = function() end + cache.get_contest_data = function() + return nil + end + scrape.scrape_contest_metadata = function(platform, contest_id) + return { + success = true, + problems = { + { id = 'x', name = 'Problem X' }, + }, + } + end + + local problems = picker.get_problems_for_contest('test_platform', 'test_contest') + assert.is_table(problems) + assert.equals(1, #problems) + assert.equals('x', problems[1].id) + end) + + it('returns empty list when scraping fails', function() + local cache = require('cp.cache') + local scrape = require('cp.scrape') + + cache.load = function() end + cache.get_contest_data = function() + return nil + end + scrape.scrape_contest_metadata = function(platform, contest_id) + return { + success = false, + error = 'test error', + } + end + + local problems = picker.get_problems_for_contest('test_platform', 'test_contest') + assert.is_table(problems) + assert.equals(0, #problems) + end) + end) + + describe('setup_problem', function() + it('calls cp.handle_command with correct arguments', function() + local cp = require('cp') + local called_with = nil + + cp.handle_command = function(opts) + called_with = opts + end + + picker.setup_problem('codeforces', '1951', 'a') + + assert.is_table(called_with) + assert.is_table(called_with.fargs) + assert.equals('codeforces', called_with.fargs[1]) + assert.equals('1951', called_with.fargs[2]) + assert.equals('a', called_with.fargs[3]) + end) + end) +end) diff --git a/spec/telescope_spec.lua b/spec/telescope_spec.lua new file mode 100644 index 0000000..3be344f --- /dev/null +++ b/spec/telescope_spec.lua @@ -0,0 +1,78 @@ +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(opts, picker_opts) + 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 platform_picker function', function() + local telescope_cp = require('cp.pickers.telescope') + assert.is_table(telescope_cp) + assert.is_function(telescope_cp.platform_picker) + end) + end) +end)