local test_samurai = require("test-samurai") local core = require("test-samurai.core") local function close_output_container() local keys = vim.api.nvim_replace_termcodes("", true, false, true) local attempts = 5 while attempts > 0 do local float_win = nil for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do local cfg = vim.api.nvim_win_get_config(win) if cfg.relative ~= "" then float_win = win break end end if not float_win then break end vim.api.nvim_set_current_win(float_win) vim.api.nvim_feedkeys(keys, "x", false) vim.wait(20, function() for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do local cfg = vim.api.nvim_win_get_config(win) if cfg.relative ~= "" then return false end end return true end) attempts = attempts - 1 end end local function has_nop_mapping(buf, lhs) local check = { lhs } if lhs == "" then table.insert(check, "") end if lhs == "" then table.insert(check, "") end for _, map in ipairs(vim.api.nvim_buf_get_keymap(buf, "n")) do local matched = false for _, key in ipairs(check) do if (map.lhs or ""):lower() == key:lower() then matched = true break end end if matched then local rhs = map.rhs or "" if rhs == "" or rhs == "" or rhs == "" then return true end end end return false end describe("test-samurai public API", function() it("delegates show_output to core", function() local called = false local orig = core.show_output core.show_output = function() called = true end test_samurai.show_output() core.show_output = orig assert.is_true(called) end) it("delegates test_nearest to core.run_nearest", function() local called = false local orig = core.run_nearest core.run_nearest = function() called = true end test_samurai.test_nearest() core.run_nearest = orig assert.is_true(called) end) it("delegates test_file to core.run_file", function() local called = false local orig = core.run_file core.run_file = function() called = true end test_samurai.test_file() core.run_file = orig assert.is_true(called) end) it("delegates test_all to core.run_all", function() local called = false local orig = core.run_all core.run_all = function() called = true end test_samurai.test_all() core.run_all = orig assert.is_true(called) end) it("delegates test_last to core.run_last", function() local called = false local orig = core.run_last core.run_last = function() called = true end test_samurai.test_last() core.run_last = orig assert.is_true(called) end) it("delegates test_failed_only to core.run_failed_only", function() local called = false local orig = core.run_failed_only core.run_failed_only = function() called = true end test_samurai.test_failed_only() core.run_failed_only = orig assert.is_true(called) end) end) describe("test-samurai output formatting", function() before_each(function() test_samurai.setup() end) after_each(function() close_output_container() end) local function find_listing_win() local current = vim.api.nvim_get_current_win() local cfg = vim.api.nvim_win_get_config(current) if cfg.relative ~= "" then return current end for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do local win_cfg = vim.api.nvim_win_get_config(win) if win_cfg.relative ~= "" then return win end end return nil end it("formats JSON output as PASS/FAIL lines", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, { status = "skipped", title = "inner skip", fullName = "outer inner skip" }, { status = "failed", title = "inner 2", fullName = "outer inner 2" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_format.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_pass = false local has_skip = false local has_fail = false for _, line in ipairs(lines) do if line == "[ PASS ] - inner 1" then has_pass = true elseif line == "[ SKIP ] - inner skip" then has_skip = true elseif line == "[ FAIL ] - inner 2" then has_fail = true end end assert.is_true(has_pass) assert.is_true(has_skip) assert.is_true(has_fail) end) it("applies PASS/FAIL/SKIP highlights to parsed output", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, { status = "skipped", title = "inner skip", fullName = "outer inner skip" }, { status = "failed", title = "inner 2", fullName = "outer inner 2" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_highlight_parsed.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local ns = vim.api.nvim_create_namespace("TestSamuraiResult") local extmarks = vim.api.nvim_buf_get_extmarks(out_buf, ns, 0, -1, { details = true }) vim.fn.jobstart = orig_jobstart local groups = {} for _, mark in ipairs(extmarks) do local details = mark[4] or {} if details.hl_group then groups[details.hl_group] = true end end assert.is_true(groups.TestSamuraiResultPass) assert.is_true(groups.TestSamuraiResultFail) assert.is_true(groups.TestSamuraiResultSkip) end) it("sets pass border when there are only passing tests", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_border_pass.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 3, 0 }) core.run_nearest() local listing = find_listing_win() assert.is_not_nil(listing) local hl = vim.api.nvim_win_get_option(listing, "winhighlight") vim.fn.jobstart = orig_jobstart assert.equals("FloatBorder:TestSamuraiBorderPass", hl) end) it("sets fail border when there are failing tests", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "failed", title = "inner 2", fullName = "outer inner 2" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_border_fail.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 3, 0 }) core.run_nearest() local listing = find_listing_win() assert.is_not_nil(listing) local hl = vim.api.nvim_win_get_option(listing, "winhighlight") vim.fn.jobstart = orig_jobstart assert.equals("FloatBorder:TestSamuraiBorderFail", hl) end) it("does not print raw JSON output for jest runs", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_raw_json.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 3, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_raw_json = false for _, line in ipairs(lines) do if line == json then has_raw_json = true break end end assert.is_false(has_raw_json) end) it("formats multi-line jest JSON output and ignores non-JSON lines", function() local lines_out = { "Some log line", "{", ' "testResults": [', " {", ' "assertionResults": [', ' {"status":"passed","title":"inner 1","fullName":"outer inner 1"}', " ]", " }", " ]", "}", } local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, lines_out, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_multiline_json.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 3, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_pass = false local has_raw_json = false for _, line in ipairs(lines) do if line == "[ PASS ] - inner 1" then has_pass = true elseif line == ' "testResults": [' then has_raw_json = true end end assert.is_true(has_pass) assert.is_false(has_raw_json) end) it("formats jest verbose output as PASS/FAIL/SKIP lines", function() local check = string.char(0xE2, 0x9C, 0x93) local cross = string.char(0xE2, 0x9C, 0x95) local circle = string.char(0xE2, 0x97, 0x8B) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { " PASS /tmp/output_verbose.test.ts", " " .. check .. " inner 1 (5 ms)", " " .. circle .. " inner skip (skipped)", " " .. cross .. " inner 2 (1 ms)", }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_verbose.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_pass = false local has_skip = false local has_fail = false local has_raw_verbose = false for _, line in ipairs(lines) do if line == "[ PASS ] - inner 1" then has_pass = true elseif line == "[ SKIP ] - inner skip" then has_skip = true elseif line == "[ FAIL ] - inner 2" then has_fail = true elseif line:match("^%s*PASS%s+") and line ~= " PASS 1 - SKIPPED 1 - FAILED 1" then has_raw_verbose = true end end assert.is_true(has_pass) assert.is_true(has_skip) assert.is_true(has_fail) assert.is_false(has_raw_verbose) end) it("formats jest JSON output for TSamAll as full names", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, { status = "skipped", title = "inner skip", fullName = "outer inner skip" }, { status = "failed", title = "inner 2", fullName = "outer inner 2" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_fullname_all.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_all() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_pass = false local has_skip = false local has_fail = false for _, line in ipairs(lines) do if line == "[ PASS ] - outer inner 1" then has_pass = true elseif line == "[ SKIP ] - outer inner skip" then has_skip = true elseif line == "[ FAIL ] - outer inner 2" then has_fail = true end end assert.is_true(has_pass) assert.is_true(has_skip) assert.is_true(has_fail) end) it("formats jest verbose output for TSamAll as short names", function() local check = string.char(0xE2, 0x9C, 0x93) local cross = string.char(0xE2, 0x9C, 0x95) local circle = string.char(0xE2, 0x97, 0x8B) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { " PASS /tmp/output_verbose_all.test.ts", " " .. check .. " inner 1 (5 ms)", " " .. circle .. " inner skip (skipped)", " " .. cross .. " inner 2 (1 ms)", }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_verbose_all.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_all() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_pass = false local has_skip = false local has_fail = false local has_raw_verbose = false for _, line in ipairs(lines) do if line == "[ PASS ] - inner 1" then has_pass = true elseif line == "[ SKIP ] - inner skip" then has_skip = true elseif line == "[ FAIL ] - inner 2" then has_fail = true elseif line:match("^%s*PASS%s+") and line ~= " PASS 1 - SKIPPED 1 - FAILED 1" then has_raw_verbose = true end end assert.is_true(has_pass) assert.is_true(has_skip) assert.is_true(has_fail) assert.is_false(has_raw_verbose) end) it("adds a summary line for TSamFile output", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, { status = "skipped", title = "inner skip", fullName = "outer inner skip" }, { status = "failed", title = "inner 2", fullName = "outer inner 2" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local orig_hrtime = vim.loop.hrtime local hr_calls = 0 vim.loop.hrtime = function() hr_calls = hr_calls + 1 if hr_calls == 1 then return 1000000000 end return 3500000000 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_summary_file.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart vim.loop.hrtime = orig_hrtime local has_total = false local has_breakdown = false local has_duration = false local saw_blank = false for _, line in ipairs(lines) do if line == "" then saw_blank = true elseif line == "TOTAL 3" then has_total = true elseif line == "DURATION 00m 02s" then has_duration = true elseif line == " PASS 1 - SKIPPED 1 - FAILED 1" then has_breakdown = true end end assert.is_true(saw_blank) assert.is_true(has_total) assert.is_true(has_breakdown) assert.is_true(has_duration) end) it("adds a summary line for TSamAll output", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, { status = "skipped", title = "inner skip", fullName = "outer inner skip" }, { status = "failed", title = "inner 2", fullName = "outer inner 2" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local orig_hrtime = vim.loop.hrtime local hr_calls = 0 vim.loop.hrtime = function() hr_calls = hr_calls + 1 if hr_calls == 1 then return 500000000 end return 2000000000 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_summary_all.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_all() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart vim.loop.hrtime = orig_hrtime local has_total = false local has_breakdown = false local has_duration = false local saw_blank = false for _, line in ipairs(lines) do if line == "" then saw_blank = true elseif line == "TOTAL 3" then has_total = true elseif line == "DURATION 00m 01s" then has_duration = true elseif line == " PASS 1 - SKIPPED 1 - FAILED 1" then has_breakdown = true end end assert.is_true(saw_blank) assert.is_true(has_total) assert.is_true(has_breakdown) assert.is_true(has_duration) end) it("adds a summary line for TSamNearest output", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, { status = "skipped", title = "inner skip", fullName = "outer inner skip" }, { status = "failed", title = "inner 2", fullName = "outer inner 2" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local orig_hrtime = vim.loop.hrtime local hr_calls = 0 vim.loop.hrtime = function() hr_calls = hr_calls + 1 if hr_calls == 1 then return 2000000000 end return 4500000000 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_summary_nearest.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 3, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart vim.loop.hrtime = orig_hrtime local has_total = false local has_breakdown = false local has_duration = false local saw_blank = false for _, line in ipairs(lines) do if line == "" then saw_blank = true elseif line == "TOTAL 3" then has_total = true elseif line == "DURATION 00m 02s" then has_duration = true elseif line == " PASS 1 - SKIPPED 1 - FAILED 1" then has_breakdown = true end end assert.is_true(saw_blank) assert.is_true(has_total) assert.is_true(has_breakdown) assert.is_true(has_duration) end) it("applies summary highlights for TSamAll output", function() local json = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, { status = "skipped", title = "inner skip", fullName = "outer inner skip" }, { status = "failed", title = "inner 2", fullName = "outer inner 2" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local orig_hrtime = vim.loop.hrtime local hr_calls = 0 vim.loop.hrtime = function() hr_calls = hr_calls + 1 if hr_calls == 1 then return 1000000000 end return 2500000000 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_summary_highlight_all.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_all() local out_buf = vim.api.nvim_get_current_buf() local ns = vim.api.nvim_create_namespace("TestSamuraiSummary") local extmarks = vim.api.nvim_buf_get_extmarks(out_buf, ns, 0, -1, { details = true }) vim.fn.jobstart = orig_jobstart vim.loop.hrtime = orig_hrtime local groups = {} for _, mark in ipairs(extmarks) do local details = mark[4] or {} if details.hl_group then groups[details.hl_group] = true end end assert.is_true(groups.TestSamuraiSummaryBold) assert.is_true(groups.TestSamuraiSummaryPass) assert.is_true(groups.TestSamuraiSummaryFail) assert.is_true(groups.TestSamuraiSummarySkip) end) it("derives summary highlight colors from diagnostic fallbacks", function() local orig_get_hl = vim.api.nvim_get_hl local orig_set_hl = vim.api.nvim_set_hl local set_calls = {} vim.api.nvim_get_hl = function(_ns, opts) if opts.name == "DiffAdd" or opts.name == "DiffDelete" then return {} end if opts.name == "DiagnosticOk" then return { fg = 111 } end if opts.name == "DiagnosticError" then return { fg = 222 } end if opts.name == "DiagnosticInfo" then return { fg = 333 } end if opts.name == "Normal" then return { fg = 99 } end return {} end vim.api.nvim_set_hl = function(_ns, name, opts) set_calls[name] = opts end core.setup() vim.api.nvim_get_hl = orig_get_hl vim.api.nvim_set_hl = orig_set_hl assert.is_true(set_calls.TestSamuraiSummaryPass.fg == 111) assert.is_true(set_calls.TestSamuraiSummaryFail.fg == 222) assert.is_true(set_calls.TestSamuraiSummarySkip.fg == 333) end) it("formats go subtests as short names", function() local json_line = vim.json.encode({ Action = "pass", Test = "TestHandleGet/returns_200", }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json_line }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_go_short_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { "package main", "import \"testing\"", "", "func TestHandleGet(t *testing.T) {", " t.Run(\"returns_200\", func(t *testing.T) {", " -- inside test", " })", "}", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 6, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_pass = false local has_raw_json = false for _, line in ipairs(lines) do if line == "[ PASS ] - returns_200" then has_pass = true elseif line == json_line then has_raw_json = true end end assert.is_true(has_pass) assert.is_false(has_raw_json) end) it("does not print raw JSON output for mocha json-stream", function() test_samurai.setup({ runner_modules = { "test-samurai.runners.js-mocha", }, }) local json_line = vim.json.encode({ event = "pass", fullTitle = "outer inner 1", }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json_line }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_raw_json.test.js") vim.bo[bufnr].filetype = "javascript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 3, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_raw_json = false for _, line in ipairs(lines) do if line == json_line then has_raw_json = true break end end assert.is_false(has_raw_json) end) it("formats mocha json-stream array output as PASS/FAIL lines", function() test_samurai.setup({ runner_modules = { "test-samurai.runners.js-mocha", }, }) local pass_line = vim.json.encode({ "pass", { title = "GET: /", fullTitle = "API :: /brands... GET: /", }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { pass_line }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_mocha_json_stream.test.js") vim.bo[bufnr].filetype = "javascript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 3, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_pass = false local has_raw_json = false for _, line in ipairs(lines) do if line == "[ PASS ] - GET: /" then has_pass = true elseif line == pass_line then has_raw_json = true end end assert.is_true(has_pass) assert.is_false(has_raw_json) end) it("does not print raw JSON when JSON arrives on stdout and stderr", function() test_samurai.setup({ runner_modules = { "test-samurai.runners.js-jest", }, }) local json1 = vim.json.encode({ testResults = { { assertionResults = { { status = "passed", title = "inner 1", fullName = "outer inner 1" }, }, }, }, }) local json2 = vim.json.encode({ testResults = { { assertionResults = { { status = "failed", title = "inner 2", fullName = "outer inner 2" }, }, }, }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { json1 }, nil) end if opts and opts.on_stderr then opts.on_stderr(1, { json2 }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_raw_json_both.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_raw_json = false for _, line in ipairs(lines) do if line == json1 or line == json2 then has_raw_json = true break end end assert.is_false(has_raw_json) end) it("handles mixed mocha json-stream events and tracks failures for failed-only", function() test_samurai.setup({ runner_modules = { "test-samurai.runners.js-mocha", }, }) local pass_line = vim.json.encode({ event = "pass", title = "inner 1", fullTitle = "outer inner 1", }) local fail_line = vim.json.encode({ event = "fail", title = "inner 2", fullTitle = "outer inner 2", }) local job_calls = {} local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(cmd, opts) table.insert(job_calls, { cmd = cmd, opts = opts }) if #job_calls == 1 then if opts and opts.on_stdout then opts.on_stdout(1, { pass_line, fail_line }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end else if opts and opts.on_exit then opts.on_exit(1, 1, nil) end end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_mixed_json.test.js") vim.bo[bufnr].filetype = "javascript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) core.run_failed_only() vim.fn.jobstart = orig_jobstart local has_pass = false local has_fail = false for _, line in ipairs(lines) do if line == "[ PASS ] - inner 1" then has_pass = true elseif line == "[ FAIL ] - inner 2" then has_fail = true end end assert.is_true(has_pass) assert.is_true(has_fail) assert.is_true(#job_calls >= 2) local failed_cmd = job_calls[2].cmd or {} local saw_grep = false local saw_fgrep = false local saw_title = false for _, arg in ipairs(failed_cmd) do if arg == "--grep" then saw_grep = true elseif arg == "--fgrep" then saw_fgrep = true elseif arg == "outer inner 2" then saw_title = true end end assert.is_false(saw_grep) assert.is_true(saw_fgrep) assert.is_true(saw_title) end) it("formats TAP output as PASS/FAIL lines", function() test_samurai.setup({ runner_modules = { "test-samurai.runners.js-vitest", }, }) local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { "TAP version 13", "1..2", "ok 1 - outer > inner 1 # time=1.00ms", "ok 2 - outer > inner skip # SKIP not now", "not ok 2 - outer > inner 2 # time=2.00ms", }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_format.tap.test.ts") vim.bo[bufnr].filetype = "typescript" vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'describe("outer", function() {', ' it("inner 1", function() {', " -- inside 1", " })", "", ' it("inner 2", function() {', " -- inside 2", " })", "})", }) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_win_set_cursor(0, { 7, 0 }) core.run_nearest() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) vim.fn.jobstart = orig_jobstart local has_pass = false local has_skip = false local has_fail = false for _, line in ipairs(lines) do if line == "[ PASS ] - inner 1" then has_pass = true elseif line == "[ SKIP ] - inner skip" then has_skip = true elseif line == "[ FAIL ] - inner 2" then has_fail = true end end assert.is_true(has_pass) assert.is_true(has_skip) assert.is_true(has_fail) end) end) describe("test-samurai output detail view", function() before_each(function() test_samurai.setup() end) after_each(function() close_output_container() end) local function find_float_wins() local wins = vim.api.nvim_tabpage_list_wins(0) local out = {} for _, win in ipairs(wins) do local cfg = vim.api.nvim_win_get_config(win) if cfg.relative ~= "" then table.insert(out, win) end end return out end local function find_detail_win(listing) for _, win in ipairs(find_float_wins()) do if win ~= listing then return win end end return nil end it("opens failing test output in a right vsplit", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = " foo_test.go:10: expected\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local wins = find_float_wins() assert.equals(2, #wins) local right = find_detail_win(output_win) assert.is_not_nil(right) local left_cfg = vim.api.nvim_win_get_config(output_win) local right_cfg = vim.api.nvim_win_get_config(right) assert.is_true(left_cfg.border == "rounded" or type(left_cfg.border) == "table") assert.is_true(right_cfg.border == "rounded" or type(right_cfg.border) == "table") assert.equals(left_cfg.row, right_cfg.row) assert.equals(left_cfg.height, right_cfg.height) assert.is_true(right_cfg.col >= left_cfg.col + left_cfg.width + 2) local total_width = left_cfg.width + right_cfg.width local expected_left = math.floor(total_width * 0.25) assert.is_true(math.abs(left_cfg.width - expected_left) <= 1) local right_buf = vim.api.nvim_win_get_buf(right) local detail = vim.api.nvim_buf_get_lines(right_buf, 0, -1, false) assert.are.same({ "=== RUN TestFoo/Sub", " foo_test.go:10: expected", }, detail) vim.fn.jobstart = orig_jobstart end) it("reuses an existing right split for test output", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = " foo_test.go:10: expected\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_reuse_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local output_win = vim.api.nvim_get_current_win() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local wins = find_float_wins() local right = find_detail_win(output_win) assert.is_not_nil(right) local right_buf = vim.api.nvim_win_get_buf(right) vim.api.nvim_buf_set_lines(right_buf, 0, -1, false, { "old output" }) vim.api.nvim_set_current_win(output_win) core.open_test_output_at_cursor() local updated = find_float_wins() local updated_right = find_detail_win(output_win) assert.equals(right, updated_right) local detail = vim.api.nvim_buf_get_lines(right_buf, 0, -1, false) assert.are.same({ "=== RUN TestFoo/Sub", " foo_test.go:10: expected", }, detail) vim.fn.jobstart = orig_jobstart end) it("translates ANSI codes into highlights for detail output", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestAnsi/Sub", Output = "\27[31mFAIL\27[0m bad\n" }), vim.json.encode({ Action = "fail", Test = "TestAnsi/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_ansi_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local wins = find_float_wins() local right = find_detail_win(output_win) assert.is_not_nil(right) local right_buf = vim.api.nvim_win_get_buf(right) local detail = vim.api.nvim_buf_get_lines(right_buf, 0, -1, false) assert.are.same({ "FAIL bad" }, detail) local ns = vim.api.nvim_get_namespaces()["TestSamuraiDetailAnsi"] assert.is_not_nil(ns) local marks = vim.api.nvim_buf_get_extmarks(right_buf, ns, 0, -1, { details = true }) assert.is_true(#marks > 0) assert.is_not_nil(marks[1][4].hl_group) vim.fn.jobstart = orig_jobstart end) it("opens detail output for PASS entries", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "PASS detail\n" }), vim.json.encode({ Action = "pass", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_pass_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ PASS %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local wins = find_float_wins() assert.equals(2, #wins) local right = find_detail_win(output_win) assert.is_not_nil(right) local right_buf = vim.api.nvim_win_get_buf(right) local detail = vim.api.nvim_buf_get_lines(right_buf, 0, -1, false) assert.are.same({ "PASS detail" }, detail) vim.fn.jobstart = orig_jobstart end) it("colors detail border based on selected result", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Pass", Output = "PASS detail\n" }), vim.json.encode({ Action = "pass", Test = "TestFoo/Pass" }), vim.json.encode({ Action = "output", Test = "TestFoo/Fail", Output = "FAIL detail\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Fail" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_border_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local pass_line = nil local fail_line = nil for i, line in ipairs(lines) do if line:match("^%[ PASS %] %- ") then pass_line = i elseif line:match("^%[ FAIL %] %- ") then fail_line = i end end assert.is_not_nil(pass_line) assert.is_not_nil(fail_line) local output_win = vim.api.nvim_get_current_win() vim.api.nvim_win_set_cursor(0, { pass_line, 0 }) core.open_test_output_at_cursor() local detail_win = find_detail_win(output_win) assert.is_not_nil(detail_win) local pass_hl = vim.api.nvim_win_get_option(detail_win, "winhighlight") assert.equals("FloatBorder:TestSamuraiBorderPass", pass_hl) vim.api.nvim_set_current_win(output_win) vim.api.nvim_win_set_cursor(0, { fail_line, 0 }) core.open_test_output_at_cursor() local fail_hl = vim.api.nvim_win_get_option(detail_win, "winhighlight") assert.equals("FloatBorder:TestSamuraiBorderFail", fail_hl) vim.fn.jobstart = orig_jobstart end) it("does not open detail output for SKIP entries", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "skip detail\n" }), vim.json.encode({ Action = "skip", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_skip_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ SKIP %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local wins = find_float_wins() assert.equals(1, #wins) local right = find_detail_win(output_win) assert.is_nil(right) vim.fn.jobstart = orig_jobstart end) it("closes detail float and restores listing width", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_close_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local wins = find_float_wins() assert.equals(2, #wins) local right = find_detail_win(output_win) assert.is_not_nil(right) local left_cfg = vim.api.nvim_win_get_config(output_win) local right_cfg = vim.api.nvim_win_get_config(right) local total_width = left_cfg.width + right_cfg.width vim.api.nvim_win_close(right, true) local remaining = find_float_wins() assert.equals(1, #remaining) local listing_cfg = vim.api.nvim_win_get_config(output_win) assert.is_true(listing_cfg.border == "rounded" or type(listing_cfg.border) == "table") assert.equals(total_width + 2, listing_cfg.width) vim.fn.jobstart = orig_jobstart end) it("closes detail float with and restores listing width", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_close_key_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local wins = find_float_wins() local detail_win = find_detail_win(output_win) assert.is_not_nil(detail_win) local left_cfg = vim.api.nvim_win_get_config(output_win) local right_cfg = vim.api.nvim_win_get_config(detail_win) local total_width = left_cfg.width + right_cfg.width vim.api.nvim_set_current_win(detail_win) local keys = vim.api.nvim_replace_termcodes("", true, false, true) vim.api.nvim_feedkeys(keys, "x", false) vim.wait(20, function() return #find_float_wins() == 1 end) local remaining = find_float_wins() assert.equals(1, #remaining) local listing_cfg = vim.api.nvim_win_get_config(output_win) assert.equals(total_width + 2, listing_cfg.width) vim.fn.jobstart = orig_jobstart end) it("disables ctrl-j/ctrl-k mappings in listing and detail buffers", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_ctrl_map_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local listing_buf = vim.api.nvim_get_current_buf() assert.is_true(has_nop_mapping(listing_buf, "")) assert.is_true(has_nop_mapping(listing_buf, "")) local lines = vim.api.nvim_buf_get_lines(listing_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local detail_win = find_detail_win(output_win) assert.is_not_nil(detail_win) local detail_buf = vim.api.nvim_win_get_buf(detail_win) assert.is_true(has_nop_mapping(detail_buf, "")) assert.is_true(has_nop_mapping(detail_buf, "")) vim.fn.jobstart = orig_jobstart end) it("focuses listing with from detail", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_focus_h_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local detail_win = find_detail_win(output_win) assert.is_not_nil(detail_win) vim.api.nvim_set_current_win(detail_win) local keys = vim.api.nvim_replace_termcodes("", true, false, true) vim.api.nvim_feedkeys(keys, "x", false) vim.wait(20, function() return vim.api.nvim_get_current_win() == output_win end) assert.equals(output_win, vim.api.nvim_get_current_win()) vim.fn.jobstart = orig_jobstart end) it("focuses detail with from listing when detail is visible", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_focus_l_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local detail_win = find_detail_win(output_win) assert.is_not_nil(detail_win) vim.api.nvim_set_current_win(output_win) local keys = vim.api.nvim_replace_termcodes("", true, false, true) vim.api.nvim_feedkeys(keys, "x", false) vim.wait(20, function() return vim.api.nvim_get_current_win() == detail_win end) assert.equals(detail_win, vim.api.nvim_get_current_win()) vim.fn.jobstart = orig_jobstart end) it("expands detail to full width and toggles with z", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_full_width_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local detail_win = find_detail_win(output_win) assert.is_not_nil(detail_win) local left_cfg = vim.api.nvim_win_get_config(output_win) local right_cfg = vim.api.nvim_win_get_config(detail_win) local total_width = left_cfg.width + right_cfg.width vim.api.nvim_set_current_win(output_win) local leader_keys = vim.api.nvim_replace_termcodes("z", true, false, true) vim.api.nvim_feedkeys(leader_keys, "x", false) vim.wait(20, function() local updated_left = vim.api.nvim_win_get_config(output_win) return updated_left.width <= 1 end) local collapsed_left = vim.api.nvim_win_get_config(output_win) local expanded_right = vim.api.nvim_win_get_config(detail_win) assert.is_true(collapsed_left.width <= 1) assert.is_true(expanded_right.width >= total_width - 1) vim.api.nvim_set_current_win(detail_win) local leader_toggle = vim.api.nvim_replace_termcodes("z", true, false, true) vim.api.nvim_feedkeys(leader_toggle, "x", false) vim.wait(20, function() local updated_left = vim.api.nvim_win_get_config(output_win) return updated_left.width > 1 end) local toggle_left = vim.api.nvim_win_get_config(output_win) local toggle_right = vim.api.nvim_win_get_config(detail_win) local toggle_total = toggle_left.width + toggle_right.width local toggle_expected = math.floor(toggle_total * 0.25) assert.is_true(math.abs(toggle_left.width - toggle_expected) <= 1) vim.fn.jobstart = orig_jobstart end) it("closes floats via ", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "pass", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 0, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_close_container_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local wins = find_float_wins() assert.equals(1, #wins) local keys = vim.api.nvim_replace_termcodes("", true, false, true) vim.api.nvim_feedkeys(keys, "x", false) vim.wait(20, function() return #find_float_wins() == 0 end) local remaining = find_float_wins() assert.equals(0, #remaining) vim.fn.jobstart = orig_jobstart end) it("disables hardtime in listing/detail and restores on close", function() local orig_hardtime = package.loaded["hardtime"] local disable_calls = 0 local enable_calls = 0 local hardtime_state = true local hardtime = { is_plugin_enabled = true, disable = function() disable_calls = disable_calls + 1 hardtime_state = false hardtime.is_plugin_enabled = false end, enable = function() enable_calls = enable_calls + 1 hardtime_state = true hardtime.is_plugin_enabled = true end, } package.loaded["hardtime"] = hardtime local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_hardtime_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) core.open_test_output_at_cursor() assert.is_true(disable_calls >= 1) assert.is_false(hardtime_state) local keys = vim.api.nvim_replace_termcodes("", true, false, true) vim.api.nvim_feedkeys(keys, "x", false) vim.wait(50, function() return #find_float_wins() == 0 end) assert.equals(1, enable_calls) assert.is_true(hardtime_state) vim.fn.jobstart = orig_jobstart package.loaded["hardtime"] = orig_hardtime end) it("closes detail when listing float is closed", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_close_listing_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() vim.api.nvim_win_close(output_win, true) local remaining = find_float_wins() assert.equals(0, #remaining) vim.fn.jobstart = orig_jobstart end) it("keeps floats open when navigating out of floats", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_nav_close_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) core.open_test_output_at_cursor() local non_float = nil for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do local cfg = vim.api.nvim_win_get_config(win) if cfg.relative == "" then non_float = win break end end assert.is_not_nil(non_float) vim.api.nvim_set_current_win(non_float) local remaining = find_float_wins() assert.equals(2, #remaining) vim.fn.jobstart = orig_jobstart end) it("keeps floats open when focusing listing from detail", function() local orig_jobstart = vim.fn.jobstart vim.fn.jobstart = function(_cmd, opts) if opts and opts.on_stdout then opts.on_stdout(1, { vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }), vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }), }, nil) end if opts and opts.on_exit then opts.on_exit(1, 1, nil) end return 1 end local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_focus_listing_test.go") vim.bo[bufnr].filetype = "go" vim.api.nvim_set_current_buf(bufnr) test_samurai.test_file() local out_buf = vim.api.nvim_get_current_buf() local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) local target = nil for i, line in ipairs(lines) do if line:match("^%[ FAIL %] %- ") then target = i break end end assert.is_not_nil(target) vim.api.nvim_win_set_cursor(0, { target, 0 }) local output_win = vim.api.nvim_get_current_win() core.open_test_output_at_cursor() local wins = find_float_wins() local detail_win = find_detail_win(output_win) assert.is_not_nil(detail_win) vim.api.nvim_set_current_win(detail_win) core.focus_listing() assert.equals(output_win, vim.api.nvim_get_current_win()) local remaining = find_float_wins() assert.equals(2, #remaining) vim.fn.jobstart = orig_jobstart end) end)