Neovim pluginのvisual regression testを書く


リファクタリングやパフォーマンス改善の際、
表示が意図せず変わってないかを手動で確認したくない。

ということでNeovim pluginのvisual regression test用ライブラリ virtes.nvim を書いた。

以下のように、シナリオ内でscreenshot()した箇所での表示を変更前後で比較できる。
差分が存在したら表示確認用のscriptが出力されるので、sourceして表示確認ができる。

screenshot.lua
local myplugin = function() -- テスト対象
  vim.cmd("tabedit")
end

local test = require("virtes").setup({
  scenario = function(ctx) -- pluginを動かすシナリオ
    ctx:screenshot()

    myplugin()
    ctx:screenshot()

    myplugin()
    ctx:screenshot()
  end,
})
local before = test:run({hash = "HEAD^"})
local after = test:run({hash = nil})

before:diff(after):write_replay_script()
nvim --clean -n +'lua dofile("screenshot.lua")' +quitall!

指定したhashごとにansi escape codeが含まれたテキストファイルが作成されるので、
catすると(今使ってるターミナルでの)表示を再現できる。
これはnvim__screenshot()を使って出力している。(公開APIっぽくないprefixなので今後変わる可能性あり)

$ tree
.
├── screenshot.lua
└── spec
    └── screenshot
        ├── HEAD
        │   ├── 1
        │   ├── 2
        │   └── 3
        ├── HEAD^
        │   ├── 1
        │   ├── 2
        │   └── 3
        └── replay.vim

その時点でのsnapshotをリポジトリにpushするわけでもなく、ヘビーに使ってないので完成度は低い。
しかし,highlightを多用するpluginに対してピンポイントの確認に使うだけでも楽できて便利。

動機の補足

extmarkでの装飾をテストする場合、
素直にやるならextmarkを取得して中身が正しいかassertする必要がある。

local ns = vim.api.nvim_create_namespace("myplugin")
vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
vim.api.nvim_buf_set_extmark(0, ns, 0, 0, {virt_text = {{"myplugin message", "Comment"}}})
vim.api.nvim_buf_set_extmark(0, ns, 0, 0, {end_col = 5, hl_group = "Todo"})

vim.api.nvim_buf_get_extmarks(0, ns, 0, -1, {details = true})
-- {
--   {1, 0, 0, {end_col = 0, end_row = 0, virt_text = {{"myplugin message", "Comment"}}}},
--   {2, 0, 0, {end_col = 5, end_row = 0, hl_group = "Todo"}},
-- }

指定位置のセルの情報を取得する方法も存在するようだが、
どちらにせよ装飾があるべき位置を指定するのが面倒。

vim.api.nvim__inspect_cell(0, 0, 0))
-- {
--   " ",
--   {background = 3359058, bold = true, foreground = 16703361},
--   {{hi_name = "TabLineSel", id = 37, kind = "syntax"}},
-- }

結局テストが落ちたときには表示を確認したくなるはずなので、
もう最初から画面全体に差分があるかをテストしたほうが楽そうだった。