diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1530cbc..c20ec27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: GH_TOKEN: ${{ github.token }} NEOVIM_PATH: ${{ github.workspace }}/neovim run: | - make patch-runner + # make patch-runner cd neovim TEST_FILE=test/functional/example_spec.lua make functionaltest diff --git a/Makefile b/Makefile index 872e06c..8d8e59f 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,25 @@ -.PHONY: test patch-runner +.PHONY: validate test -# nvim -c "set runtimepath+=." -l test/gh_spec.lua +TEST_CWD := $(shell pwd) + +validate: +ifeq ($(NEOVIM_PATH),) + $(error NEOVIM_PATH is not set. Export it or pass it like: make NEOVIM_PATH=/path/to/neovim test) +endif # Run tests with "nvim -l" instead of "-ll", so tests can use the full "vim." # API from the Nvim test harness process itself. -patch-runner: - @if grep -q -- "-ll " "$(NEOVIM_PATH)/cmake/RunTests.cmake"; then \ - echo "Patching RunTests.cmake (-ll → -l)..."; \ - sed -i.bak 's/-ll /-l /' "$(NEOVIM_PATH)/cmake/RunTests.cmake" && rm -f "$(NEOVIM_PATH)/cmake/RunTests.cmake.bak"; \ - else \ - echo "RunTests.cmake already patched; skipping."; \ - fi +# patch-runner: validate +# @if grep -q -- "-ll " "$(NEOVIM_PATH)/cmake/RunTests.cmake"; then \ +# echo "Patching RunTests.cmake (-ll → -l)..."; \ +# sed -i.bak 's/-ll /-l /' "$(NEOVIM_PATH)/cmake/RunTests.cmake" && rm -f "$(NEOVIM_PATH)/cmake/RunTests.cmake.bak"; \ +# else \ +# echo "RunTests.cmake already patched; skipping."; \ +# fi -test: patch-runner -ifeq ($(NEOVIM_PATH),) - $(error NEOVIM_PATH is not set) -endif +test: validate ln -sf $(shell pwd) $(NEOVIM_PATH)/test/functional/guh.nvim @cd $(NEOVIM_PATH) && env GH_TOKEN=$${GH_TOKEN:-$(shell gh auth token 2>/dev/null)} \ + TEST_CWD=$(TEST_CWD) \ TEST_FILE=test/functional/guh.nvim/test/gh_spec.lua make functionaltest diff --git a/README.md b/README.md index 363228c..088cf45 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,16 @@ Requirements: without doing a checkout. 5. (TODO) PR comments will display on relevant git objects. +## Development + +Run the tests: + + NEOVIM_PATH='/path/to/neovim/' make test + +Run specific tests: + + NEOVIM_PATH='/path/to/neovim/' make test TEST_FILTER=load_comments + ## Related - https://github.com/pwntester/octo.nvim diff --git a/test/gh_spec.lua b/test/gh_spec.lua index 891ca76..0193c45 100644 --- a/test/gh_spec.lua +++ b/test/gh_spec.lua @@ -1,269 +1,224 @@ ---@diagnostic disable: redundant-return-value --- TODO: do this in global test setup -vim.opt.runtimepath:append{ - vim.fn.getcwd() .. '/test/functional/guh.nvim/', -} - -local t = require('test.testutil') -local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local clear, eq, eval, command, feed, insert, wait = n.clear, t.eq, n.eval, n.command, n.feed, n.insert, n.wait - -local async = require('async') -local gh = require('guh.gh') -local pr_commands = require('guh.pr_commands') -local utils = require('guh.utils') -local state = require('guh.state') -local system_str_async = async.wrap(2, utils.system_str) -local get_pr_info_async = async.wrap(2, gh.get_pr_info) -local get_issue_async = async.wrap(2, gh.get_issue) +local n = require('test.functional.testnvim')() +local t = require('test.testutil') -local tests = {} +local test_cwd = assert(os.getenv('TEST_CWD')) -function tests.test_get_pr_info() - return async.run(function() - local result = assert(system_str_async('gh pr list --json number')) - local pr_num = assert(vim.json.decode(result)[1].number, 'failed to get a repo issue') +local screen +before_each(function() + n.clear{ + args = { + '-c', + ("cd '%s'"):format(test_cwd) + } + } + n.exec [[ + set laststatus=2 + ]] + screen = Screen.new(80, 10) + n.exec_lua(function(cwd_) + vim.cmd.cd(cwd_) + -- TODO: do this in global test setup + vim.opt.runtimepath:append{ + cwd_, + -- vim.fn.getcwd() .. '/test/functional/guh.nvim/', + } + require('guh').setup({}) + end, test_cwd) +end) - local pr = get_pr_info_async(pr_num) - assert(pr, 'pr is nil') - assert(type(pr.number) == 'number', 'pr.number not number') - assert(type(pr.title) == 'string', 'pr.title not string') - assert(type(pr.author) == 'table', 'pr.author not table') +describe('guh.gh', function() + it('get_pr_info', function() + n.exec_lua(function() + local async = require('async') + local gh = require('guh.gh') + local utils = require('guh.utils') + local system_str_async = async.wrap(2, utils.system_str) + local get_pr_info_async = async.wrap(2, gh.get_pr_info) + + local function test_get_pr_info() + return async.run(function() + local result = assert(system_str_async('gh pr list --json number')) + local pr_num = assert(vim.json.decode(result)[1].number, 'failed to get a repo issue') + + local pr = get_pr_info_async(pr_num) + assert(pr, 'pr is nil') + assert(type(pr.number) == 'number', 'pr.number not number') + assert(type(pr.title) == 'string', 'pr.title not string') + assert(type(pr.author) == 'table', 'pr.author not table') + end) + end + local task = test_get_pr_info() + task:wait(5000) + end) end) -end -function tests.test_get_issue() - return async.run(function() - local result = system_str_async('gh issue list --json number') - local issue_num = assert(vim.json.decode(assert(result))[1].number, 'failed to get a repo issue') + it('get_issue', function() + n.exec_lua(function() + local async = require('async') + local gh = require('guh.gh') + local utils = require('guh.utils') + local system_str_async = async.wrap(2, utils.system_str) + local get_issue_async = async.wrap(2, gh.get_issue) + + local function test_get_issue() + return async.run(function() + local result = system_str_async('gh issue list --json number') + local issue_num = assert(vim.json.decode(assert(result))[1].number, 'failed to get a repo issue') + + local issue = get_issue_async(issue_num) + assert(issue, 'issue is nil') + assert(type(issue.number) == 'number', 'issue.number not number') + assert(type(issue.title) == 'string', 'issue.title not string') + assert(type(issue.author) == 'table', 'issue.author not table') + end) + end - local issue = get_issue_async(issue_num) - assert(issue, 'issue is nil') - assert(type(issue.number) == 'number', 'issue.number not number') - assert(type(issue.title) == 'string', 'issue.title not string') - assert(type(issue.author) == 'table', 'issue.author not table') + local task = test_get_issue() + task:wait(5000) + end) end) -end - --- Tests hardcoded diff. -function tests.test_get_prepare_comment() - local pr_id = 42 - local buf = state.get_buf('diff', pr_id) - state.show_buf(buf) - state.set_b_guh(buf, { - id = pr_id, - feat = 'diff', - }) - - vim.api.nvim_buf_set_lines(buf, 0, -1, false, { - "diff --git a/lua/guh/config.lua b/lua/guh/config.lua", - "index a573cc0..2cedcc0 100644", - "--- a/lua/guh/config.lua", - "+++ b/lua/guh/config.lua", - "@@ -13,6 +13,7 @@ M.s = {", - " html_comments_command = { 'lynx', '-stdin', '-dump' },", - " keymaps = {", - " diff = {", - "+ comment = 'cc',", - " open_file = 'gf',", - }) - vim.api.nvim_win_set_cursor(0, {9, 0}) -- on "+ comment = 'cc'," - - local info = pr_commands.prepare_to_comment(9, 9) - assert(info) - assert('lua/guh/config.lua' == info.file) - assert(16 == info.start_line, info.start_line) - assert(16 == info.end_line, info.end_line) - assert(pr_id == info.pr_id, info.pr_id) - assert(buf == info.buf, info.buf) -end - --- Tests real comments response Github. --- Calls load_comments() and asserts that some comments were loaded into quickfix. -function tests.test_get_comments() - return async.run(function() - local result = system_str_async('gh pr list --json number') - local prs = assert(vim.json.decode(assert(result)), 'failed to get PRs') - assert(#prs > 0, 'no PRs found') - local pr_num = prs[1].number - return async.await(function(callback) - vim.schedule(function() - require('guh.comments').load_comments(pr_num) - - -- Wait for quickfix to be populated or timeout - local ok = vim.wait(5000, function() - local qf = vim.fn.getqflist() - return #qf > 0 + it('load_comments', function() + n.exec_lua(function() + local async = require('async') + local gh = require('guh.gh') + local load_comments_async = async.wrap(2, gh.load_comments) + + local function test_get_issue() + return async.run(function() + local pr_id = 1 + local comments = load_comments_async(pr_id) + assert(comments) + assert(vim.tbl_count(comments) > 0) + -- error(vim.tbl_keys(comments)[1]) end) + end - if ok then - local qf = vim.fn.getqflist() - assert(#qf > 0, 'quickfix list is empty') - -- Check that entries have filename, lnum, text - for _, entry in ipairs(qf) do - assert(entry.filename, 'entry missing filename') - assert(entry.lnum > 0, 'entry lnum not positive') - assert(entry.text, 'entry missing text') - end - else - -- If no comments, that's ok, but notify - print('No comments found for PR ' .. pr_num .. ', test passed but no assertions') - end - - callback() - end) + local task = test_get_issue() + task:wait(5000) end) end) -end - --- Tests that ":Guh 1" shows the issue in a buffer. -function tests.test_Guh() - return async.run(function() - local result = assert(system_str_async('gh issue list --json number')) - local issue_num = assert(vim.json.decode(result)[1].number, 'failed to get a repo issue') - - return async.await(function(callback) - vim.schedule(function() - -- Run the command - vim.cmd(('Guh %d'):format(issue_num)) +end) - -- Wait for the buffer to be created - local ok, buf = vim.wait(5000, function() - local b = vim.fn.bufnr(('guh://issue/%d'):format(issue_num)) - return b > 0, b +describe('comments', function() + pending('load_comments', function() + n.exec_lua(function() + local async = require('async') + + -- Tests real comments response Github. + -- Calls load_comments() and asserts that some comments were loaded into quickfix. + local function test_load_comments() + return async.run(function() + -- local result = system_str_async('gh pr list --json number') + -- local prs = assert(vim.json.decode(assert(result)), 'failed to get PRs') + -- assert(#prs > 0, 'no PRs found') + local pr_num = 2 + + require('guh.comments').load_comments(pr_num) + + -- Wait for quickfix to be populated or timeout + local ok = vim.wait(5000, function() + local qf = vim.fn.getqflist() + return #qf > 0 + end) + + if ok then + local qf = vim.fn.getqflist() + assert(#qf > 0, 'quickfix list is empty') + -- Check that entries have filename, lnum, text + for _, entry in ipairs(qf) do + assert(entry.filename, 'entry missing filename') + assert(entry.lnum > 0, 'entry lnum not positive') + assert(entry.text, 'entry missing text') + end + else + error('No comments found for PR ' .. pr_num) + end end) - assert(ok) + end - -- Check the buffer content - ---@diagnostic disable-next-line param-type-mismatch - local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) - assert(#lines > 0, 'buffer is empty') - assert(lines[1]:match('#%d'), 'first line not issue title') - callback() - end) + local task = test_load_comments() + local ok, err = task:wait(5000) + assert(ok, err or 'test_load_comments failed') end) end) -end +end) -describe('guh.gh', function() - local screen - before_each(function() - clear() - screen = Screen.new(80, 10) - n.exec_lua[[ - -- TODO: do this in global test setup - vim.opt.runtimepath:append{ - vim.fn.getcwd() .. '/test/functional/guh.nvim/', - } - require('guh').setup({}) - ]] +describe('features', function() + it('prepare_to_comment (hardcoded diff)', function() + n.exec_lua(function() + local pr_commands = require('guh.pr_commands') + local state = require('guh.state') + + local pr_id = 42 + local buf = state.get_buf('diff', pr_id) + state.show_buf(buf) + state.set_b_guh(buf, { + id = pr_id, + feat = 'diff', + }) + + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { + 'diff --git a/lua/guh/config.lua b/lua/guh/config.lua', + 'index a573cc0..2cedcc0 100644', + '--- a/lua/guh/config.lua', + '+++ b/lua/guh/config.lua', + '@@ -13,6 +13,7 @@ M.s = {', + " html_comments_command = { 'lynx', '-stdin', '-dump' },", + ' keymaps = {', + ' diff = {', + "+ comment = 'cc',", + " open_file = 'gf',", + }) + vim.api.nvim_win_set_cursor(0, { 9, 0 }) -- on "+ comment = 'cc'," + + local info = pr_commands.prepare_to_comment(9, 9) + assert(info) + assert('lua/guh/config.lua' == info.file) + assert(16 == info.start_line, info.start_line) + assert(16 == info.end_line, info.end_line) + assert(pr_id == info.pr_id, info.pr_id) + assert(buf == info.buf, info.buf) + end) end) +end) +describe('commands', function() it(':GuhDiff', function() n.command('GuhDiff 1') - -- screen:snapshot_util() - screen:expect{ + screen:expect { attr_ids = {}, -- Don't care about colors. grid = [[ ^diff --git {MATCH:a/.* b/.* +}| - new file mode {MATCH:%d+ +}| index {MATCH:.*}| --- {MATCH:.*}| +++ {MATCH:.*}| - @@ {MATCH:.*} @@ {MATCH: +}| + @@ {MATCH:.*} @@ {MATCH:.*}| + {MATCH:.*}|*3 + {MATCH:guh://diff/1 .*}| {MATCH:.*}| - {MATCH:.*}| - {MATCH:.*}| - {MATCH:.*}| - ]] + ]], } end) - pending('get_pr_info', function() - local task = tests.test_get_pr_info() - local ok, err = task:wait(5000) - assert(ok, err or 'get_pr_info failed') - end) - - pending('get_issue', function() - local task = tests.test_get_issue() - local ok, err = task:wait(5000) - assert(ok, err or 'get_issue failed') - end) - - it('prepare_to_comment (static diff)', function() - tests.test_get_prepare_comment({}) - end) + it('":Guh 1" shows issue/pr', function() + n.command('Guh 1') - pending('get_comments', function() - local task = tests.test_get_comments() - local ok, err = task:wait(5000) - assert(ok, err or 'get_comments failed') - end) + screen:expect { + attr_ids = {}, -- Don't care about colors. + grid = [[ + ^{MATCH:.*#1 +}| + {MATCH:.*}|*7 + {MATCH:guh://.*/1 .*}| + {MATCH: +}| + ]], + } - pending('Guh command shows issue buffer', function() - local task = tests.test_Guh() - local ok, err = task:wait(5000) - assert(ok, err or 'Guh command failed') + -- local buf = n.fn.bufname('%') + -- t.ok(buf == 'guh://issue/1' or buf == 'guh://pr/1', 'guh://{pr,issue}/1', buf) end) end) - ---[[ -local function main() - require('guh').setup({}) - -- tests = { test_get_prepare_comment2 = tests.test_get_prepare_comment2 } - - -- Check if CWD is a GitHub repo that gh can work with - -- local is_repo = async - -- .run(function() - -- return not not system_str_async('gh repo view --json nameWithOwner') - -- end) - -- :wait() - -- if not is_repo then - -- print('Not a GitHub repository or gh not configured') - -- return - -- end - - ---@type table - local tasks = {} - for testname, _ in pairs(tests) do - tasks[testname] = {} - end - -- Start all tests as parallel tasks. Pass a context which they can augment. - for testname, testfn in vim.spairs(tests) do - local ctx = tasks[testname] - ctx.task = testfn(ctx) - end - - local all_passed = true - async - .run(function() - for testname, ctx in vim.spairs(tasks) do - local err - if type(ctx.task) == 'table' and ctx.task.pwait then - ctx.passed, err = ctx.task:pwait() - else - -- For non-async tests (which would have exited the process before now). - ctx.passed = true - end - local name = ('%s%s'):format(testname, ctx.desc and (' %s'):format(ctx.desc) or '') - print(('%s: %s'):format(ctx.passed and 'pass' or 'fail', name)) - if not ctx.passed and err then - print(vim.text.indent(2, err)) - end - all_passed = all_passed and not not ctx.passed - end - print('') - end) - :wait() - if not all_passed then - os.exit(1) - end -end - -main() ---]]