local test_samurai = require("test-samurai") local core = require("test-samurai.core") 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) 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("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+") 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+") 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 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)