vusted & nvim-kitでユーザーの入力をテストする
はじめに
これは Vim駅伝 4/7の記事です。
みなさん、プラグイン作ってますか?
テスト、書いてますか?
今回は Neovim Lua でのテストについて、簡単に触れようかと思います。
vusted (busted)
Vim プラグインにテストを書くなら vim-themis が良いでしょう。
ですが、Lua で書いたプラグインならテストも Lua で書きたいですよね。
そこで vusted が便利です。
作者本人の紹介記事はこちら
busted を nvim --headless でラップしたもので、CIでも動かせます。
公式のドキュメントの他、日本語記事も幾つかありますので busted の使い方は省略します。
大体 JavaScript の mocha です。
describe("foo", function()
  it("bar", function()
    local bar = "bar"
    assert.are.equals("bar", bar)
  end)
  it("baz", function()
    vim.api.nvim_buf_set_lines(0, 0, -1, true, { "baz" })
    assert.are.equals("baz", vim.api.nvim_get_current_line())
  end)
end)
nvim-kit
nvim-kit は、埋め込み型の Lua utilities です。
:KitInstall で自身のプラグインに追加できます。
これは私のプラグイン ccc.nvim のディレクトリ構造の一部です。
 lua
└──  ccc
   ├──  kit
   │  ├──  App
   │  ├──  Async
   │  ├──  init.lua
   │  ├──  IO
   │  ├──  LSP
   │  ├──  Thread
   │  └──  Vim
色々と便利なモジュールが入っています。
vusted でテストが書かれているので、使い方はそちらを確認しましょう。
今回使うのは Vim.Keymap です。
feedkeys() によるキー入力は色々と嵌りどころが多く、そのまま使うのは大変です。
このモジュールは、非同期、同期での入力をサポートします。
使い方
キー入力を送信するためには Keymap.send() を使います。
そのまま使うと非同期になるので、同期させたい場合は :await() を付けます。
またテストで使うときは、Keymap.spec() の中で使いましょう。
渡す文字列はそのまま解釈されるので、特殊なキーを送信したいときは Keymap.termcodes() を挟む必要があります。
またデフォルトでは remap されません。remap させたい時は引数をテーブルにします。
引数には、それらの配列を取ることもできます。
nvim-kit は全て annotation がついてますので、困ったら型を見ましょう。
- keymap_spec.lua
 
vim.opt.runtimepath:append("/path/to/nvim-kit")
local Keymap = require("___kit___.kit.Vim.Keymap")
local t = Keymap.termcodes
local Async = require('___kit___.kit.Async')
describe("Keymap", function()
  before_each(function()
    vim.cmd("enew!")
  end)
  it("insert", function()
    Keymap.spec(function()
      Keymap.send(t("ifoo<Esc>")):await()
      assert.are.equals("foo", vim.api.nvim_get_current_line())
    end)
  end)
  it("remap", function()
    Keymap.spec(function()
      vim.keymap.set("i", "jj", "<Esc>")
      Keymap.send({ keys = "ifoojj", remap = true }):await()
      assert.are.equals("foo", vim.api.nvim_get_current_line())
    end)
  end)
  it('should send multiple keys in sequence', function()
    vim.keymap.set(
      'i',
      '(',
      Async.async(function()
        Keymap.send('('):await()
        assert.equals('[(', vim.api.nvim_get_current_line())
        Keymap.send(')'):await()
        assert.equals('[()', vim.api.nvim_get_current_line())
        Keymap.send(t('<Left>a<Right>')):await()
        assert.equals('[(a)', vim.api.nvim_get_current_line())
      end)
    )
    Keymap.spec(function()
      Keymap.send('i'):await()
      Keymap.send({ '[', { keys = '(', remap = true }, ']' }):await()
      assert.equals('[(a)]', vim.api.nvim_get_current_line())
    end)
  end)
end)
3つ目のテストは本家の Keymap_spec.lua から持ってきました。
Keymap.send():await() は、複雑な入力でもきちんと同期的に処理されていることが分かります。
また Keymap.spec() から抜けると insert mode から normal mode に自動で戻されます。
なので一つめのテストを以下のように変えても、それ以降のテストはちゃんと通ります。
    Keymap.spec(function()
      Keymap.send("ifoo"):await()
      assert.are.equals("foo", vim.api.nvim_get_current_line())
    end)
終わりに
これでコマンドやマッピングのテストが簡単に書けるようになりました。
作者のお二人には足を向けて寝れません 🙏
Discussion