Skip to content

Commit 2b28090

Browse files
authored
feat: add tests (#4)
following recommendations from https://github.com/echasnovski/mini.nvim/blob/main/readmes/mini-test.md, tries to set up tests
1 parent 43c50d1 commit 2b28090

15 files changed

+308
-16
lines changed

.github/workflows/main.yml

+33-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ on:
66
pull_request:
77
types: [opened, synchronize]
88

9+
concurrency:
10+
group: ${{ github.head_ref }}
11+
cancel-in-progress: true
12+
913
jobs:
1014
lint:
1115
runs-on: ubuntu-latest
12-
name: Lint
16+
name: lint
1317
steps:
1418
- uses: actions/checkout@v3
1519

@@ -18,3 +22,31 @@ jobs:
1822
token: ${{ secrets.GITHUB_TOKEN }}
1923
version: latest
2024
args: --check .
25+
26+
tests:
27+
needs: lint
28+
runs-on: ubuntu-latest
29+
timeout-minutes: 2
30+
strategy:
31+
matrix:
32+
neovim_version: ['v0.7.2', 'v0.8.1', 'nightly']
33+
34+
steps:
35+
- uses: actions/checkout@v3
36+
37+
- run: date +%F > todays-date
38+
39+
- name: restore cache for today's nightly.
40+
uses: actions/cache@v3
41+
with:
42+
path: _neovim
43+
key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }}
44+
45+
- name: setup neovim
46+
uses: rhysd/action-setup-vim@v1
47+
with:
48+
neovim: true
49+
version: ${{ matrix.neovim_version }}
50+
51+
- name: run tests
52+
run: make test

Makefile

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
GROUP_DEPTH ?= 1
2+
NVIM_EXEC ?= nvim
3+
4+
all: test documentation
5+
6+
test:
7+
$(NVIM_EXEC) --version | head -n 1 && echo ''
8+
$(NVIM_EXEC) --headless --noplugin -u ./scripts/minimal_init.lua \
9+
-c "lua require('mini.test').setup()" \
10+
-c "lua MiniTest.run({ execute = { reporter = MiniTest.gen_reporter.stdout({ group_depth = $(GROUP_DEPTH) }) } })"
11+
12+
documentation:
13+
$(NVIM_EXEC) --headless --noplugin -u ./scripts/minimal_init.lua -c "lua require('mini.doc').generate()" -c "qa!"
14+
15+
lint:
16+
stylua .

deps/mini.nvim

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit d00abe8169993b95f52ff64fbfe685f353fd1c4f

doc/no-neck-pain.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vim:tw=78:ts=8:noet:ft=help:norl:

doc/tags

Whitespace-only changes.

lua/no-neck-pain/config.lua

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
local config = {}
1+
local cfg = {}
22

3-
config.options = {
3+
cfg = {
44
width = 100,
55
debug = false,
66
}
77

8-
function config.setup(opts)
9-
config.options = vim.tbl_deep_extend("keep", opts or {}, config.options)
8+
function cfg.setup(opts)
9+
cfg = vim.tbl_deep_extend("keep", opts or {}, cfg)
1010
end
1111

12-
return config
12+
return cfg

lua/no-neck-pain/init.lua

+21-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
1-
local M = {}
1+
local NNP = {}
22

3-
function M.start()
4-
require("no-neck-pain.main").toggle()
3+
local cfg = require("no-neck-pain.config")
4+
local main = require("no-neck-pain.main")
5+
6+
NNP.config = cfg
7+
8+
NNP.state = main.state
9+
10+
NNP.fns = {
11+
main.disable,
12+
main.enable,
13+
main.toggle,
14+
}
15+
16+
function NNP.start()
17+
main.toggle()
518
end
619

7-
function M.setup(opts)
8-
require("no-neck-pain.config").setup(opts)
20+
function NNP.setup(opts)
21+
cfg.setup(opts)
922
end
1023

11-
return M
24+
_G.NoNeckPain = NNP
25+
26+
return NNP

lua/no-neck-pain/main.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
local cfg = require("no-neck-pain.config").options
1+
local cfg = require("no-neck-pain.config")
22
local util = require("no-neck-pain.util")
33
local SIDES = { "left", "right" }
44

lua/no-neck-pain/util.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
local cfg = require("no-neck-pain.config").options
1+
local cfg = require("no-neck-pain.config")
22
local M = {}
33

44
-- print only if debug is true

plugin/no-neck-pain.lua

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
if vim.g.noNeckPain then
1+
if _G.noNeckPainLoaded then
22
return
33
end
4-
vim.g.noNeckPain = true
4+
5+
_G.noNeckPainLoaded = true
56

67
vim.api.nvim_create_user_command("NoNeckPain", function()
78
require("no-neck-pain").start()

scripts/minimal_init.lua

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- Add project root as full path to runtime path (in order to be able to
2+
-- `require()`) modules from this module
3+
vim.cmd([[let &rtp.=','.getcwd()]])

scripts/minitest.lua

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
local minitest = require("mini.test")
2+
3+
if _G.MiniTest == nil then
4+
minitest.setup()
5+
end
6+
minitest.run()

stylua.toml

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
indent_type = "Spaces"
22
indent_width = 4
33
column_width = 100
4+
quote_style = "AutoPreferDouble"
5+
no_call_parentheses = false

tests/helpers.lua

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
-- imported from https://github.com/echasnovski/mini.nvim
2+
3+
local Helpers = {}
4+
5+
-- Add extra expectations
6+
Helpers.expect = vim.deepcopy(MiniTest.expect)
7+
8+
Helpers.expect.match = MiniTest.new_expectation("string matching", function(str, pattern)
9+
return str:find(pattern) ~= nil
10+
end, function(str, pattern)
11+
return string.format("Pattern: %s\nObserved string: %s", vim.inspect(pattern), str)
12+
end)
13+
14+
Helpers.expect.no_match = MiniTest.new_expectation("no string matching", function(str, pattern)
15+
return str:find(pattern) == nil
16+
end, function(str, pattern)
17+
return string.format("Pattern: %s\nObserved string: %s", vim.inspect(pattern), str)
18+
end)
19+
20+
-- Monkey-patch `MiniTest.new_child_neovim` with helpful wrappers
21+
Helpers.new_child_neovim = function()
22+
local child = MiniTest.new_child_neovim()
23+
24+
local prevent_hanging = function(method)
25+
-- stylua: ignore
26+
if not child.is_blocked() then return end
27+
28+
local msg =
29+
string.format("Can not use `child.%s` because child process is blocked.", method)
30+
error(msg)
31+
end
32+
33+
child.setup = function()
34+
child.restart({ "-u", "scripts/minimal_init.lua" })
35+
36+
-- Change initial buffer to be readonly. This not only increases execution
37+
-- speed, but more closely resembles manually opened Neovim.
38+
child.bo.readonly = false
39+
end
40+
41+
child.set_lines = function(arr, start, finish)
42+
prevent_hanging("set_lines")
43+
44+
if type(arr) == "string" then
45+
arr = vim.split(arr, "\n")
46+
end
47+
48+
child.api.nvim_buf_set_lines(0, start or 0, finish or -1, false, arr)
49+
end
50+
51+
child.get_lines = function(start, finish)
52+
prevent_hanging("get_lines")
53+
54+
return child.api.nvim_buf_get_lines(0, start or 0, finish or -1, false)
55+
end
56+
57+
child.set_cursor = function(line, column, win_id)
58+
prevent_hanging("set_cursor")
59+
60+
child.api.nvim_win_set_cursor(win_id or 0, { line, column })
61+
end
62+
63+
child.get_cursor = function(win_id)
64+
prevent_hanging("get_cursor")
65+
66+
return child.api.nvim_win_get_cursor(win_id or 0)
67+
end
68+
69+
child.set_size = function(lines, columns)
70+
prevent_hanging("set_size")
71+
72+
if type(lines) == "number" then
73+
child.o.lines = lines
74+
end
75+
76+
if type(columns) == "number" then
77+
child.o.columns = columns
78+
end
79+
end
80+
81+
child.get_size = function()
82+
prevent_hanging("get_size")
83+
84+
return { child.o.lines, child.o.columns }
85+
end
86+
87+
--- Assert visual marks
88+
---
89+
--- Useful to validate visual selection
90+
---
91+
---@param first number|table Table with start position or number to check linewise.
92+
---@param last number|table Table with finish position or number to check linewise.
93+
---@private
94+
child.expect_visual_marks = function(first, last)
95+
child.ensure_normal_mode()
96+
97+
first = type(first) == "number" and { first, 0 } or first
98+
last = type(last) == "number" and { last, 2147483647 } or last
99+
100+
MiniTest.expect.equality(child.api.nvim_buf_get_mark(0, "<"), first)
101+
MiniTest.expect.equality(child.api.nvim_buf_get_mark(0, ">"), last)
102+
end
103+
104+
-- Work with 'mini.nvim':
105+
-- - `mini_load` - load with "normal" table config
106+
-- - `mini_load_strconfig` - load with "string" config, which is still a
107+
-- table but with string values. Final loading is done by constructing
108+
-- final string table. Needed to be used if one of the config entries is a
109+
-- function (as currently there is no way to communicate a function object
110+
-- through RPC).
111+
-- - `mini_unload` - unload module and revert common side effects.
112+
child.mini_load = function(name, config)
113+
local lua_cmd = ([[require('mini.%s').setup(...)]]):format(name)
114+
child.lua(lua_cmd, { config })
115+
end
116+
117+
child.mini_load_strconfig = function(name, strconfig)
118+
local t = {}
119+
for key, val in pairs(strconfig) do
120+
table.insert(t, key .. " = " .. val)
121+
end
122+
local str = string.format("{ %s }", table.concat(t, ", "))
123+
124+
local command = ([[require('mini.%s').setup(%s)]]):format(name, str)
125+
child.lua(command)
126+
end
127+
128+
child.mini_unload = function(name)
129+
local module_name = "mini." .. name
130+
local tbl_name = "Mini" .. name:sub(1, 1):upper() .. name:sub(2)
131+
132+
-- Unload Lua module
133+
child.lua(([[package.loaded['%s'] = nil]]):format(module_name))
134+
135+
-- Remove global table
136+
child.lua(("_G[%s] = nil"):format(tbl_name))
137+
138+
-- Remove autocmd group
139+
if child.fn.exists("#" .. tbl_name) == 1 then
140+
-- NOTE: having this in one line as `'augroup %s | au! | augroup END'`
141+
-- for some reason seemed to sometimes not execute `augroup END` part.
142+
-- That lead to a subsequent bare `au ...` calls to be inside `tbl_name`
143+
-- group, which gets empty after every `require(<module_name>)` call.
144+
child.cmd(("augroup %s"):format(tbl_name))
145+
child.cmd("au!")
146+
child.cmd("augroup END")
147+
end
148+
end
149+
150+
child.expect_screenshot = function(opts, path, screenshot_opts)
151+
if child.fn.has("nvim-0.8") == 0 then
152+
MiniTest.skip("Screenshots are tested for Neovim>=0.8 (for simplicity).")
153+
end
154+
155+
MiniTest.expect.reference_screenshot(child.get_screenshot(screenshot_opts), path, opts)
156+
end
157+
158+
return child
159+
end
160+
161+
-- Mark test failure as "flaky"
162+
Helpers.mark_flaky = function()
163+
MiniTest.finally(function()
164+
if #MiniTest.current.case.exec.fails > 0 then
165+
MiniTest.add_note("This test is flaky.")
166+
end
167+
end)
168+
end
169+
170+
return Helpers

tests/test_API.lua

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
local helpers = dofile("tests/helpers.lua")
2+
3+
local child = helpers.new_child_neovim()
4+
local _, eq = helpers.expect, helpers.expect.equality
5+
local new_set, _ = MiniTest.new_set, MiniTest.finally
6+
7+
local T = new_set({
8+
hooks = {
9+
-- This will be executed before every (even nested) case
10+
pre_case = function()
11+
-- Restart child process with custom 'init.lua' script
12+
child.restart({ "-u", "scripts/minimal_init.lua" })
13+
-- Load tested plugin
14+
child.lua([[M = require('no-neck-pain')]])
15+
end,
16+
-- This will be executed one after all tests from this set are finished
17+
post_once = child.stop,
18+
},
19+
})
20+
21+
T["setup()"] = new_set()
22+
23+
T["setup()"]["sets global variable"] = function()
24+
eq(child.lua_get("type(_G.noNeckPainLoaded)"), "boolean")
25+
end
26+
27+
T["setup()"]["check exposed fields"] = function()
28+
eq(child.lua_get("type(_G.NoNeckPain)"), "table")
29+
30+
-- exposed fns
31+
eq(child.lua_get("type(_G.NoNeckPain.start)"), "function")
32+
eq(child.lua_get("type(_G.NoNeckPain.setup)"), "function")
33+
34+
-- config
35+
eq(child.lua_get("type(_G.NoNeckPain.config)"), "table")
36+
37+
local expect_config = function(field, value)
38+
eq(child.lua_get("_G.NoNeckPain.config." .. field), value)
39+
end
40+
41+
expect_config("width", 100)
42+
expect_config("debug", false)
43+
end
44+
45+
return T

0 commit comments

Comments
 (0)