diff --git a/.busted b/.busted index 61d98b0..7a5de61 100644 --- a/.busted +++ b/.busted @@ -1,8 +1,8 @@ return { _all = { coverage = false, - lpath = "lua/?.lua;lua/?/init.lua", - lua = "~/.luarocks/bin/nlua", + lpath = "./lua_modules/share/lua/5.1/?.lua;./lua_modules/share/lua/5.1/?/init.lua;lua/?.lua;lua/?/init.lua", + lua = "./lua_modules/bin/nlua", }, default = { verbose = true diff --git a/.luacheckrc b/.luacheckrc index b7475c5..6c79945 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,7 +1,8 @@ ---@diagnostic disable-next-line lowercase-global std = { - globals = { "vim", "describe", "it" }, + globals = { "vim", "describe", "it", "setup" }, read_globals = { + "setup", "require", "error", "table", diff --git a/adopure.nvim-0.0.1-1.rockspec b/adopure.nvim-0.0.1-1.rockspec index 50d70fb..6b061be 100644 --- a/adopure.nvim-0.0.1-1.rockspec +++ b/adopure.nvim-0.0.1-1.rockspec @@ -11,6 +11,11 @@ dependencies = { } test_dependencies = { "nlua", + "plenary.nvim", + "nui.nvim", + "telescope.nvim", + "LuaCov", + "nvim-nio", } build = { type = "builtin", diff --git a/lua/ado.lua b/lua/ado.lua index 5eb26a8..1dc151c 100644 --- a/lua/ado.lua +++ b/lua/ado.lua @@ -19,7 +19,7 @@ end ---@param sub_impl string[] ---@param subcommand_args string[] ---@param subcommand string -local function prompt_target(sub_impl, subcommand_args, subcommand) +local function execute_or_prompt(sub_impl, subcommand_args, subcommand) if #subcommand_args == 0 then vim.ui.select(vim.tbl_keys(sub_impl), { "Choose target" }, function(choice) if not choice then @@ -30,7 +30,9 @@ local function prompt_target(sub_impl, subcommand_args, subcommand) end) return end - local load_opts = function() return load("return " .. (subcommand_args[2] or ""), 't')() end + local load_opts = function() + return load("return " .. (subcommand_args[2] or ""), "t")() + end local ok, opts = pcall(load_opts) sub_impl[subcommand_args[1]](ok and opts or {}) end @@ -39,6 +41,17 @@ end ---@field impl fun(args:string[]) ---@field complete_args? string[] +---Initial load of state_manager with all open PRs; +---@return StateManager +function M.load_state_manager() + if not state_manager then + local context = require("ado.state").AdoContext:new() + state_manager = require("ado.state").StateManager:new(context) + end + assert(state_manager, "StateManager should not be nil after loading;") + return state_manager +end + ---@type table local subcommand_tbl = { load = { @@ -46,17 +59,14 @@ local subcommand_tbl = { impl = function(args) local sub_impl = { context = function(opts) - if not state_manager then - local context = require("ado.state").AdoContext:new() - state_manager = require("ado.state").StateManager:new(context) - end - state_manager:choose_and_activate(opts) + local manager = M.load_state_manager() + manager:choose_and_activate(opts) end, threads = function(opts) M.get_loaded_state():load_pull_request_threads(opts) end, } - prompt_target(sub_impl, args, "load") + execute_or_prompt(sub_impl, args, "load") end, }, submit = { @@ -73,7 +83,7 @@ local subcommand_tbl = { require("ado.thread").update_thread_status(M.get_loaded_state(), opts) end, } - prompt_target(sub_impl, args, "submit") + execute_or_prompt(sub_impl, args, "submit") end, }, open = { @@ -93,7 +103,7 @@ local subcommand_tbl = { require("ado.thread").open_thread_window(M.get_loaded_state(), opts) end, } - prompt_target(sub_impl, args, "open") + execute_or_prompt(sub_impl, args, "open") end, }, } diff --git a/lua/ado/api.lua b/lua/ado/api.lua index 09ca4f8..feb92f8 100644 --- a/lua/ado/api.lua +++ b/lua/ado/api.lua @@ -108,14 +108,14 @@ function M.get_pull_requests_iteration_changes(pull_request, iteration) return result.changeEntries, err end ----Get pull request threads from Azure DevOps ----@param pull_request PullRequest +---Get not deleted pull request threads from Azure DevOps +---@param state AdoState ---@return Thread[] threads, string|nil err -function M.get_pull_request_threads(pull_request) - local iteration = "$baseIteration=1&iteration=6" +function M.get_pull_request_threads(state) + local iteration = "$baseIteration=1&iteration=" .. state.active_pull_request_iteration.id ---@type RequestResult local result, err = get_azure_devops( - pull_request.url .. "/threads?" .. table.concat({ GIT_API_VERSION, iteration }, "&"), + state.active_pull_request.url .. "/threads?" .. table.concat({ GIT_API_VERSION, iteration }, "&"), "pull request threads" ) if not result then @@ -124,7 +124,12 @@ function M.get_pull_request_threads(pull_request) ---@type Thread[] local threads = result.value - return threads, err + local active_threads = vim.iter(threads) + :filter(function(thread) ---@param thread Thread + return not thread.isDeleted + end) + :totable() + return active_threads, err end ---patch request to azure devops @@ -145,9 +150,12 @@ local function submit_azure_devops(url, http_verb, request_type, body) end return nil, "Failed to " .. http_verb .. " " .. request_type .. "; " .. details end - ---@type Thread|Comment|Reviewer|nil - local result = vim.fn.json_decode(response.body) - return result, nil + if type(response.body) == "string" and #response.body ~=0 then + ---@type Thread|Comment|Reviewer|nil + local result = vim.fn.json_decode(response.body) + return result, nil + end + return nil, nil end ---Create new pull request comment thread @@ -198,6 +206,22 @@ function M.update_pull_request_thread(pull_request, thread) return result, err end +---Create new pull request comment reply +---@param pull_request PullRequest +---@param thread Thread +---@param comment_id number +---@return Thread|nil thread, string|nil err +function M.delete_pull_request_comment(pull_request, thread, comment_id) + ---@type Thread|nil + local result, err = submit_azure_devops( + table.concat({ pull_request.url, "/threads/", thread.id, "/comments/", comment_id, "?", GIT_API_VERSION }), + "DELETE", + "pull request thread update", + nil + ) + return result, err +end + ---Create new pull request vote ---@param pull_request PullRequest ---@param vote PullRequestVote @@ -221,4 +245,5 @@ function M.submit_vote(pull_request, vote) ) return result, err end + return M diff --git a/lua/ado/config/internal.lua b/lua/ado/config/internal.lua index 8d2386d..9668ef3 100644 --- a/lua/ado/config/internal.lua +++ b/lua/ado/config/internal.lua @@ -19,7 +19,7 @@ function InternalConfig:access_token() "or check the docs to find other ways of configuring it.", }, " ") assert(self.pat_token, message) - return require("b64").enc(":" .. self.pat_token) + return vim.base64.encode(":" .. self.pat_token) end local config = InternalConfig:new() diff --git a/lua/ado/git.lua b/lua/ado/git.lua index 71facee..7c2b9c1 100644 --- a/lua/ado/git.lua +++ b/lua/ado/git.lua @@ -1,20 +1,5 @@ local M = {} ----Get git repository name ----@return string Repository name -function M.get_repo_name() - local get_git_repo_job = require("plenary.job"):new({ - command = "git", - args = { "rev-parse", "--show-toplevel" }, - cwd = ".", - }) - get_git_repo_job:start() - - local repository_path = require("plenary.path"):new(require("ado.utils").await_result(get_git_repo_job)[1]) - local path_parts = vim.split(repository_path.filename, repository_path.path.sep) - return path_parts[#path_parts] -end - ---@param remote_url string ---@return string organization_url ---@return string project_name diff --git a/lua/ado/review.lua b/lua/ado/review.lua index 806a5a4..dcf0acf 100644 --- a/lua/ado/review.lua +++ b/lua/ado/review.lua @@ -30,18 +30,23 @@ end ---Submit vote of choice on pull request ---@param state AdoState ---@param _ table -function M.submit_vote(state,_) +function M.submit_vote(state, _) vim.ui.select(vim.tbl_keys(M.pull_request_vote), { prompt = "Select vote;" }, function(vote) if not vote then vim.notify("No vote chosen;", 2) return end - local reviewer, err = require("ado.api").submit_vote(state.active_pull_request, M.pull_request_vote[vote]) - - if err or not reviewer then + local new_reviewer, err = require("ado.api").submit_vote(state.active_pull_request, M.pull_request_vote[vote]) + if err or not new_reviewer then error(err or "Expected Reviewer but not nil;") end - -- TODO: Render vote somewhere? + for _, reviewer in pairs(state.active_pull_request.reviewers) do + if reviewer.displayName == new_reviewer.displayName then + reviewer.vote = new_reviewer.vote + return + end + end + table.insert(state.active_pull_request.reviewers, new_reviewer) end) end diff --git a/lua/ado/state.lua b/lua/ado/state.lua index f7e181d..486a31e 100644 --- a/lua/ado/state.lua +++ b/lua/ado/state.lua @@ -53,7 +53,7 @@ end ---@param _ table function AdoState:load_pull_request_threads(_) - local pull_request_threads, err = require("ado.api").get_pull_request_threads(self.active_pull_request) + local pull_request_threads, err = require("ado.api").get_pull_request_threads(self) if err then error(err) end diff --git a/lua/ado/thread.lua b/lua/ado/thread.lua index d7ac6ef..830caec 100644 --- a/lua/ado/thread.lua +++ b/lua/ado/thread.lua @@ -51,6 +51,16 @@ local function get_pull_request_comment_thread_context(state, thread_context) return nil, "File not changed in this Pull Request;" end +---@param bufnr number +---@param mark_id number +---@param state AdoState +---@param thread_to_open Thread +local function add_comment_reply(bufnr, mark_id, state, thread_to_open) + ---@type CommentReply + local comment_reply = { bufnr = bufnr, mark_id = mark_id, thread = thread_to_open } + table.insert(state.comment_replies, comment_reply) +end + ---@param state AdoState ---@param bufnr number ---@param comment_creation CommentCreate @@ -80,7 +90,9 @@ local function submit_thread(state, bufnr, comment_creation) return err or "Expected Thread but got nil;" end table.insert(state.pull_request_threads, thread) - require("ado.render").render_reply_thread(namespace, thread) + local mark_id + bufnr, mark_id = require("ado.render").render_reply_thread(namespace, thread) + add_comment_reply(bufnr, mark_id, state, thread) end ---@return number line_start, number col_start, number line_end, number col_end @@ -176,9 +188,7 @@ function M.open_thread_window(state, opts) return end local bufnr, mark_id = require("ado.render").render_reply_thread(namespace, thread_to_open) - ---@type CommentReply - local comment_reply = { bufnr = bufnr, mark_id = mark_id, thread = thread_to_open } - table.insert(state.comment_replies, comment_reply) + add_comment_reply(bufnr, mark_id, state, thread_to_open) end ---@param bufnr number @@ -231,8 +241,7 @@ function M.update_thread_status(state, _) end ---@type Thread - ---@diagnostic disable-next-line: missing-fields - local thread = { + local thread = { ---@diagnostic disable-line: missing-fields id = comment_reply.thread.id, status = choice, } @@ -258,6 +267,11 @@ function M.submit_comment(state, _) if err then error(err) end + state.comment_creations = vim.iter(state.comment_creations) + :filter(function(state_comment_creation) ---@param state_comment_creation CommentCreate + return comment_creation.bufnr ~= state_comment_creation.bufnr + end) + :totable() return end diff --git a/spec/example_spec.lua b/spec/example_spec.lua deleted file mode 100644 index d9b16d0..0000000 --- a/spec/example_spec.lua +++ /dev/null @@ -1,6 +0,0 @@ -describe('Test example', function() - it('Test can access vim namespace', function() - assert.are.same(vim.trim(' a '), 'a') - end) -end) -Added 29 char line at line 10 diff --git a/spec/load_spec.lua b/spec/load_spec.lua new file mode 100644 index 0000000..4a5b14e --- /dev/null +++ b/spec/load_spec.lua @@ -0,0 +1,23 @@ +describe("Load command", function() + ---@type StateManager|nil + local state_manager + setup(function() + local handle = io.popen("pass show AZURE_DEVOPS_EXT_PAT_ADOPURE") + assert(handle, "Handle not nil;") + local secret_value = handle:read() + vim.g.adopure = { pat_token = secret_value } + end) + + it("can retrieve PRs", function() + require("plenary.path") + state_manager = require("ado").load_state_manager() + assert.are.same(#state_manager.pull_requests, 1) + end) + + it("can activate a PR", function() + state_manager = require("ado").load_state_manager() + state_manager:set_state_by_choice(state_manager.pull_requests[1]) + + assert.are.same(state_manager.state.active_pull_request.title, "Updated pr-test-file.md PR title") + end) +end) diff --git a/spec/submit_spec.lua b/spec/submit_spec.lua new file mode 100644 index 0000000..34561f8 --- /dev/null +++ b/spec/submit_spec.lua @@ -0,0 +1,110 @@ +describe("submit command", function() + setup(function() ---@diagnostic disable-line: undefined-global + local handle = io.popen("pass show AZURE_DEVOPS_EXT_PAT_ADOPURE") + assert(handle, "Handle not nil;") + local secret_value = handle:read() + vim.g.adopure = { pat_token = secret_value } + end) + + local expect_message = "This will be the written comment in the PR!" + local real_nvim_get_current_buf = vim.api.nvim_get_current_buf + local real_nvim_buf_get_lines = vim.api.nvim_buf_get_lines + local real_vim_ui_select = vim.ui.select + + local function mock_functions() + vim.api.nvim_get_current_buf = function() ---@diagnostic disable-line: duplicate-set-field + return 1 + end + vim.api.nvim_buf_get_lines = function() ---@diagnostic disable-line: duplicate-set-field + return { expect_message } + end + ---@param _1 string[] + ---@param _2 table + ---@param cb fun(choice: string):nil + --- + vim.ui.select = function(_1, _2, cb) ---@diagnostic disable-line: duplicate-set-field + cb("closed") + end + end + + before_each(function() + local state_manager = require("ado").load_state_manager() + state_manager:set_state_by_choice(state_manager.pull_requests[1]) + mock_functions() + end) + + local function create_comment() + local thread_context = { + filePath = "/tests/pr-test-file.md", + leftFileStart = nil, + leftFileEnd = nil, + rightFileStart = { line = 3, offset = 2 }, + rightFileEnd = { line = 3, offset = 9 }, + } + local comment_creation = { + bufnr = 1, + mark_id = 10000, + thread_context = thread_context, + } + local state = require("ado").get_loaded_state() + table.insert(state.comment_creations, comment_creation) + require("ado.thread").submit_comment(state, {}) + end + + it("can submit a comment", function() + create_comment() + local state = require("ado").get_loaded_state() + ---@type Thread + local thread = vim.iter(state.pull_request_threads):find(function(thread) ---@param thread Thread + return thread.comments[1].commentType == "text" + end) + assert.are.same(expect_message, thread.comments[1].content) + end) + + it("can submit a reply", function() + create_comment() + local state = require("ado").get_loaded_state() + require("ado.thread").submit_comment(state, {}) + assert.are.same(2, #state.pull_request_threads[1].comments) + end) + + it("can submit a thread status change", function() + create_comment() + local state = require("ado").get_loaded_state() + require("ado.thread").update_thread_status(state, {}) + assert.are.same("closed", state.pull_request_threads[1].status) + end) + + it("can submit a pull request vote", function() + local state = require("ado").get_loaded_state() + vim.ui.select = function(_1, _2, cb) ---@diagnostic disable-line: duplicate-set-field + cb("approved") + end + require("ado.review").submit_vote(state, {}) + assert.are.same(10, state.active_pull_request.reviewers[1].vote) + vim.ui.select = function(_1, _2, cb) ---@diagnostic disable-line: duplicate-set-field + cb("no vote") + end + require("ado.review").submit_vote(state, {}) + end) + + local function unmock_functions() + vim.api.nvim_get_current_buf = real_nvim_get_current_buf + vim.api.nvim_buf_get_lines = real_nvim_buf_get_lines + vim.ui.select = real_vim_ui_select + end + + after_each(function() + local state = require("ado").get_loaded_state() + for _, thread in pairs(state.pull_request_threads) do + for _, comment in pairs(thread.comments) do + local _, err = + require("ado.api").delete_pull_request_comment(state.active_pull_request, thread, comment.id) + if err then + error(err) + end + end + end + unmock_functions() + end) +end)