diff --git a/README.md b/README.md index 81d27cc..ba874e3 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Main plugin: https://gitea.mschirmer.com/m13r/test-samurai.nvim - Builds `npx jest` commands for nearest, file, all, and failed-only runs. - Streams results via a custom Jest reporter using `onTestCaseResult`. - Uses `--testLocationInResults` for Quickfix and `o` support. +- Captures `console.*` output per test case and shows it in the detail float. ## Installation (lazy.nvim) @@ -60,3 +61,4 @@ Use the standard `test-samurai.nvim` commands (e.g. `TSamNearest`, `TSamFile`, ` - The reporter lives at `reporter/test_samurai_jest_reporter.js` and is loaded via `--reporters`. - Test names are reported as `Describe/It` for grouping in the listing. +- Stdout capture uses `reporter/test_samurai_jest_stdout.js` via `--setupFilesAfterEnv` and tags output as `TSAMURAI_STDOUT`. diff --git a/lua/test-samurai-jest-runner/init.lua b/lua/test-samurai-jest-runner/init.lua index 5aec668..78a750f 100644 --- a/lua/test-samurai-jest-runner/init.lua +++ b/lua/test-samurai-jest-runner/init.lua @@ -4,6 +4,7 @@ local runner = { } local RESULT_PREFIX = "TSAMURAI_RESULT " +local STDOUT_PREFIX = "TSAMURAI_STDOUT " local STATUS_MAP = { passed = "passes", failed = "failures", @@ -341,6 +342,15 @@ local function reporter_path() return vim.fs.normalize(dir .. "/../../reporter/test_samurai_jest_reporter.js") end +local function setup_path() + local source = debug.getinfo(1, "S").source + if source:sub(1, 1) == "@" then + source = source:sub(2) + end + local dir = vim.fs.dirname(source) + return vim.fs.normalize(dir .. "/../../reporter/test_samurai_jest_stdout.js") +end + local function base_cmd() return { "npx", @@ -348,6 +358,8 @@ local function base_cmd() "--testLocationInResults", "--reporters", reporter_path(), + "--setupFilesAfterEnv", + setup_path(), } end @@ -366,6 +378,21 @@ local function parse_result_line(line) return data end +local function parse_stdout_line(line) + if not line or line == "" then + return nil + end + if line:sub(1, #STDOUT_PREFIX) ~= STDOUT_PREFIX then + return nil + end + local payload = line:sub(#STDOUT_PREFIX + 1) + local ok, data = pcall(vim.json.decode, payload) + if not ok or type(data) ~= "table" then + return nil + end + return data +end + local function update_location_cache(name, data) if not name or name == "" then return @@ -675,9 +702,14 @@ function runner.parse_test_output(output) if not output or output == "" then return out end + local jest_to_listing = {} + local pending = {} for line in output:gmatch("[^\n]+") do local data = parse_result_line(line) if data and data.name and data.output then + if data.jestName and data.jestName ~= "" then + jest_to_listing[data.jestName] = data.name + end out[data.name] = out[data.name] or {} if type(data.output) == "string" then for _, item in ipairs(split_output_lines(data.output)) do @@ -691,6 +723,49 @@ function runner.parse_test_output(output) end end end + local stdout = parse_stdout_line(line) + if stdout and (stdout.name or stdout.jestName) and stdout.output then + local stdout_name = stdout.name or stdout.jestName + local target = jest_to_listing[stdout_name] or stdout_name + if not jest_to_listing[stdout_name] then + pending[stdout_name] = pending[stdout_name] or {} + table.insert(pending[stdout_name], stdout.output) + else + out[target] = out[target] or {} + if type(stdout.output) == "string" then + for _, item in ipairs(split_output_lines(stdout.output)) do + if item ~= "" then + table.insert(out[target], item) + end + end + elseif type(stdout.output) == "table" then + for _, item in ipairs(stdout.output) do + if item and item ~= "" then + table.insert(out[target], item) + end + end + end + end + end + end + for jest_name, outputs in pairs(pending) do + local target = jest_to_listing[jest_name] or jest_name + out[target] = out[target] or {} + for _, items in ipairs(outputs) do + if type(items) == "string" then + for _, item in ipairs(split_output_lines(items)) do + if item ~= "" then + table.insert(out[target], item) + end + end + elseif type(items) == "table" then + for _, item in ipairs(items) do + if item and item ~= "" then + table.insert(out[target], item) + end + end + end + end end return out end diff --git a/reporter/test_samurai_jest_stdout.js b/reporter/test_samurai_jest_stdout.js new file mode 100644 index 0000000..6574b28 --- /dev/null +++ b/reporter/test_samurai_jest_stdout.js @@ -0,0 +1,36 @@ +'use strict'; + +const util = require('util'); + +const STDOUT_PREFIX = 'TSAMURAI_STDOUT '; + +function getCurrentTestName() { + if (typeof expect !== 'function') return null; + try { + const state = expect.getState(); + if (state && state.currentTestName) { + return String(state.currentTestName); + } + } catch (_err) { + return null; + } + return null; +} + +function emitStdout(name, args) { + if (!name) return; + const message = util.format(...args); + if (message === '') return; + process.stdout.write(`${STDOUT_PREFIX}${JSON.stringify({ jestName: name, output: message })}\n`); +} + +function wrapConsoleMethod(method) { + if (typeof console[method] !== 'function') return; + const original = console[method].bind(console); + console[method] = (...args) => { + emitStdout(getCurrentTestName(), args); + return original(...args); + }; +} + +['log', 'info', 'warn', 'error', 'debug'].forEach(wrapConsoleMethod); diff --git a/tests/test_jest_runner_spec.lua b/tests/test_jest_runner_spec.lua index 7882943..83a8088 100644 --- a/tests/test_jest_runner_spec.lua +++ b/tests/test_jest_runner_spec.lua @@ -41,6 +41,15 @@ local function get_reporter_path() return vim.fs.normalize(dir .. "/../../reporter/test_samurai_jest_reporter.js") end +local function get_setup_path() + local source = debug.getinfo(runner.build_command, "S").source + if source:sub(1, 1) == "@" then + source = source:sub(2) + end + local dir = vim.fs.dirname(source) + return vim.fs.normalize(dir .. "/../../reporter/test_samurai_jest_stdout.js") +end + describe("test-samurai-jest-runner", function() it("detects Jest test files by suffix and package.json", function() with_project(JEST_PACKAGE, function(root) @@ -176,6 +185,8 @@ describe("test-samurai-jest-runner", function() "--testLocationInResults", "--reporters", get_reporter_path(), + "--setupFilesAfterEnv", + get_setup_path(), "--runTestsByPath", cmd_spec.cmd[#cmd_spec.cmd], }, @@ -200,6 +211,8 @@ describe("test-samurai-jest-runner", function() "--testLocationInResults", "--reporters", get_reporter_path(), + "--setupFilesAfterEnv", + get_setup_path(), "--runTestsByPath", "/tmp/math.test.js", "--testNamePattern", @@ -226,6 +239,8 @@ describe("test-samurai-jest-runner", function() "--testLocationInResults", "--reporters", get_reporter_path(), + "--setupFilesAfterEnv", + get_setup_path(), "--runTestsByPath", "/tmp/math.test.js", "--testNamePattern", @@ -253,6 +268,8 @@ describe("test-samurai-jest-runner", function() "--testLocationInResults", "--reporters", get_reporter_path(), + "--setupFilesAfterEnv", + get_setup_path(), "--runTestsByPath", root .. "/foo.test.ts", }, @@ -276,6 +293,8 @@ describe("test-samurai-jest-runner", function() "--testLocationInResults", "--reporters", get_reporter_path(), + "--setupFilesAfterEnv", + get_setup_path(), }, cmd_spec.cmd ) @@ -291,6 +310,8 @@ describe("test-samurai-jest-runner", function() "--testLocationInResults", "--reporters", get_reporter_path(), + "--setupFilesAfterEnv", + get_setup_path(), "--runTestsByPath", "/tmp/math.test.js", "--testNamePattern", @@ -310,6 +331,8 @@ describe("test-samurai-jest-runner", function() "--testLocationInResults", "--reporters", get_reporter_path(), + "--setupFilesAfterEnv", + get_setup_path(), "--runTestsByPath", "/tmp/math.test.js", "--testNamePattern", @@ -395,18 +418,24 @@ describe("test-samurai-jest-runner", function() local output = table.concat({ "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/adds", + jestName = "Math adds", status = "failed", output = { "line1", "line2" }, }), + "TSAMURAI_STDOUT " .. vim.json.encode({ + jestName = "Math adds", + output = "log line", + }), "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/adds", + jestName = "Math adds", status = "failed", output = { "line3" }, }), }, "\n") local results = runner.parse_test_output(output) - assert.are.same({ "line1", "line2", "line3" }, results["Math/adds"]) + assert.are.same({ "line1", "line2", "log line", "line3" }, results["Math/adds"]) end) it("collect_failed_locations uses cached locations", function()