local runner = require("test-samurai-jest-runner") local function write_file(path, content) local dir = vim.fn.fnamemodify(path, ":h") vim.fn.mkdir(dir, "p") vim.fn.writefile(vim.split(content, "\n"), path) end local function with_project(package_json, fn) local root = vim.fn.tempname() vim.fn.mkdir(root, "p") root = vim.loop.fs_realpath(root) or root if package_json then write_file(root .. "/package.json", package_json) end fn(root) end local JEST_PACKAGE = [[ { "devDependencies": { "jest": "^29.0.0" } } ]] local NO_JEST_PACKAGE = [[ { "devDependencies": { "mocha": "^10.0.0" } } ]] local function get_reporter_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_reporter.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) local bufnr1 = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr1, root .. "/example.test.js") assert.is_true(runner.is_test_file(bufnr1)) local bufnr2 = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr2, root .. "/example.spec.tsx") assert.is_true(runner.is_test_file(bufnr2)) local bufnr3 = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr3, root .. "/example.js") assert.is_false(runner.is_test_file(bufnr3)) end) end) it("rejects test files when jest dependency is missing", function() with_project(NO_JEST_PACKAGE, function(root) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, root .. "/example.test.js") assert.is_false(runner.is_test_file(bufnr)) end) end) it("finds nearest test with describe hierarchy", function() with_project(JEST_PACKAGE, function(root) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, root .. "/math.test.js") local lines = { "describe('Math', () => {", " test('adds', () => {", " expect(1 + 1).toBe(2)", " })", "})", } vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) local spec, err = runner.find_nearest(bufnr, 2, 0) assert.is_nil(err) assert.equals("adds", spec.test_name) assert.equals("Math/adds", spec.full_name) assert.equals("Math adds", spec.jest_name) assert.are.same({ "Math", "adds" }, spec.jest_parts) assert.is_true(spec.file:match("math%.test%.js$") ~= nil) assert.equals(root, spec.cwd) end) end) it("handles multiline describe and it declarations", function() with_project(JEST_PACKAGE, function(root) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, root .. "/multiline.test.js") local lines = { "describe(", " '
',", " () => {", " describe(", " 'renders properly...',", " () => {", " it(", " 'the teaser links',", " async () => {", " expect(true).toBe(true)", " }", " )", " }", " )", " }", ")", } vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) local spec, err = runner.find_nearest(bufnr, 9, 0) assert.is_nil(err) assert.equals("the teaser links", spec.test_name) assert.equals("
/renders properly.../the teaser links", spec.full_name) assert.are.same({ "
", "renders properly...", "the teaser links" }, spec.jest_parts) end) end) it("uses describe block when cursor is between tests", function() with_project(JEST_PACKAGE, function(root) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, root .. "/between.test.js") local lines = { "describe('
', () => {", " describe('renders properly...', () => {", " it('the logo', async () => {", " expect(true).toBe(true)", " })", " ", " it('the teaser links', async () => {", " expect(true).toBe(true)", " })", " })", "})", } vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) local spec, err = runner.find_nearest(bufnr, 5, 0) assert.is_nil(err) assert.equals("renders properly...", spec.test_name) assert.equals("
/renders properly...", spec.full_name) assert.are.same({ "
", "renders properly..." }, spec.jest_parts) assert.equals("describe", spec.kind) end) end) it("falls back to file command when outside any describe", function() with_project(JEST_PACKAGE, function(root) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, root .. "/outside.test.js") local lines = { "const value = 1", "function helper() { return value }", } vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) local spec, err = runner.find_nearest(bufnr, 1, 0) assert.is_nil(err) assert.equals("file", spec.kind) local cmd_spec = runner.build_command(spec) assert.are.same( { "npx", "jest", "--testLocationInResults", "--reporters", get_reporter_path(), "--runTestsByPath", cmd_spec.cmd[#cmd_spec.cmd], }, cmd_spec.cmd ) assert.is_true(cmd_spec.cmd[#cmd_spec.cmd]:match("outside%.test%.js$") ~= nil) end) end) it("build_command uses npx jest with reporter and pattern", function() local spec = { file = "/tmp/math.test.js", cwd = "/tmp", full_name = "Math/adds", jest_parts = { "Math", "adds" }, } local cmd_spec = runner.build_command(spec) assert.are.same( { "npx", "jest", "--testLocationInResults", "--reporters", get_reporter_path(), "--runTestsByPath", "/tmp/math.test.js", "--testNamePattern", "^.*adds$", }, cmd_spec.cmd ) assert.equals("/tmp", cmd_spec.cwd) end) it("build_command uses prefix pattern for describe blocks", function() local spec = { file = "/tmp/math.test.js", cwd = "/tmp", kind = "describe", jest_parts = { "
", "renders properly..." }, } local cmd_spec = runner.build_command(spec) local pattern = cmd_spec.cmd[#cmd_spec.cmd] assert.are.same( { "npx", "jest", "--testLocationInResults", "--reporters", get_reporter_path(), "--runTestsByPath", "/tmp/math.test.js", "--testNamePattern", pattern, }, cmd_spec.cmd ) assert.is_true(pattern:find("Header", 1, true) ~= nil) assert.is_true(pattern:find("renders", 1, true) ~= nil) assert.is_true(pattern:find("properly", 1, true) ~= nil) assert.is_true(pattern:sub(-2) == ".*") end) it("build_file_command scopes to file", function() with_project(JEST_PACKAGE, function(root) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, root .. "/foo.test.ts") local cmd_spec = runner.build_file_command(bufnr) assert.are.same( { "npx", "jest", "--testLocationInResults", "--reporters", get_reporter_path(), "--runTestsByPath", root .. "/foo.test.ts", }, cmd_spec.cmd ) assert.equals(root, cmd_spec.cwd) end) end) it("build_all_command runs project tests", function() with_project(JEST_PACKAGE, function(root) local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, root .. "/bar.test.ts") local cmd_spec = runner.build_all_command(bufnr) assert.are.same( { "npx", "jest", "--testLocationInResults", "--reporters", get_reporter_path(), }, cmd_spec.cmd ) assert.equals(root, cmd_spec.cwd) end) end) it("build_failed_command narrows to failed tests", function() local last_command = { cmd = { "npx", "jest", "--testLocationInResults", "--reporters", get_reporter_path(), "--runTestsByPath", "/tmp/math.test.js", "--testNamePattern", "^Old$", }, cwd = "/tmp", } local failures = { "Math/adds", "edge (1+1)" } local cmd_spec = runner.build_failed_command(last_command, failures, "file") local pattern = cmd_spec.cmd[#cmd_spec.cmd] assert.are.same( { "npx", "jest", "--testLocationInResults", "--reporters", get_reporter_path(), "--runTestsByPath", "/tmp/math.test.js", "--testNamePattern", pattern, }, cmd_spec.cmd ) assert.is_true(pattern:match("%^%..*adds%$") ~= nil) assert.is_true(pattern:match("edge") ~= nil) assert.is_true(pattern:find("\\(1", 1, true) ~= nil) assert.is_true(pattern:find("\\+1", 1, true) ~= nil) assert.equals("/tmp", cmd_spec.cwd) end) it("parse_results collects statuses and locations", function() local output = table.concat({ "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/adds", status = "passed", file = "/tmp/math.test.js", location = { line = 3, column = 4 }, }), "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/subtracts", status = "failed", file = "/tmp/math.test.js", location = { line = 10, column = 2 }, }), "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/skipped", status = "skipped", file = "/tmp/math.test.js", location = { line = 20, column = 2 }, }), }, "\n") local results = runner.parse_results(output) assert.are.same({ "Math/adds" }, results.passes) assert.are.same({ "Math/subtracts" }, results.failures) assert.are.same({ "Math/skipped" }, results.skips) assert.are.same({ "Math/adds" }, results.display.passes) assert.are.same({ "Math/subtracts" }, results.display.failures) assert.are.same({ "Math/skipped" }, results.display.skips) end) it("output_parser streams per test case", function() local parser = runner.output_parser() local state = {} local line = "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/adds", status = "failed", file = "/tmp/math.test.js", location = { line = 3, column = 4 }, }) local results = parser.on_line(line, state) assert.are.same({ "Math/adds" }, results.failures) assert.are.same({ "Math/adds" }, results.display.failures) assert.are.same({ "Math/adds" }, results.failures_all) assert.is_nil(parser.on_complete("", state)) end) it("keeps failures_all across non-failure lines", function() local parser = runner.output_parser() local state = {} local fail_line = "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/adds", status = "failed", }) local pass_line = "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/other", status = "passed", }) parser.on_line(fail_line, state) local results = parser.on_line(pass_line, state) assert.are.same({ "Math/adds" }, results.failures_all) end) it("parse_test_output groups output per test", function() local output = table.concat({ "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/adds", status = "failed", output = { "line1", "line2" }, }), "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/adds", status = "failed", output = { "line3" }, }), }, "\n") local results = runner.parse_test_output(output) assert.are.same({ "line1", "line2", "line3" }, results["Math/adds"]) end) it("collect_failed_locations uses cached locations", function() local output = "TSAMURAI_RESULT " .. vim.json.encode({ name = "Math/adds", status = "failed", file = "/tmp/math.test.js", location = { line = 8, column = 2 }, }) runner.parse_results(output) local items = runner.collect_failed_locations({ "Math/adds" }, nil, "file") assert.equals(1, #items) assert.equals("/tmp/math.test.js", items[1].filename) assert.equals(8, items[1].lnum) assert.equals(2, items[1].col) end) end)