retrovim 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/.conda/retrovim.yaml +259 -0
  2. package/.conda/terax.icns +0 -0
  3. package/.conda/terax.ico +0 -0
  4. package/.conda/terax.png +0 -0
  5. package/.github/workflows/release.yml +95 -0
  6. package/.gitmodules +3 -0
  7. package/.npm/kanata_touchcursor.js +28 -0
  8. package/.npm/nvim.js +26 -0
  9. package/.npm/yazi.js +26 -0
  10. package/.npm/zsh.js +17 -0
  11. package/.pip/kanata_touchcursor/__init__.py +28 -0
  12. package/.pip/nvim/__init__.py +31 -0
  13. package/.pip/yazi/__init__.py +31 -0
  14. package/.pip/zsh/__init__.py +22 -0
  15. package/CHANGELOG.md +26 -0
  16. package/LICENSE +674 -0
  17. package/README.md +336 -0
  18. package/kanata/touchcursor.kbd +164 -0
  19. package/nvim/init.lua +813 -0
  20. package/nvim/plugins/mini.nvim/.github/DISCUSSION_TEMPLATE/q-a.yml +72 -0
  21. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/bug-report.yml +136 -0
  22. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/config.yml +5 -0
  23. package/nvim/plugins/mini.nvim/.github/ISSUE_TEMPLATE/feature-request.yml +75 -0
  24. package/nvim/plugins/mini.nvim/.github/PULL_REQUEST_TEMPLATE.md +2 -0
  25. package/nvim/plugins/mini.nvim/.github/workflows/quality-control.yml +150 -0
  26. package/nvim/plugins/mini.nvim/.pre-commit-config.yaml +20 -0
  27. package/nvim/plugins/mini.nvim/.stylua.toml +7 -0
  28. package/nvim/plugins/mini.nvim/.styluaignore +1 -0
  29. package/nvim/plugins/mini.nvim/CHANGELOG.md +1894 -0
  30. package/nvim/plugins/mini.nvim/CODE_OF_CONDUCT.md +132 -0
  31. package/nvim/plugins/mini.nvim/CONTRIBUTING.md +331 -0
  32. package/nvim/plugins/mini.nvim/LICENSE +21 -0
  33. package/nvim/plugins/mini.nvim/MAINTAINING.md +330 -0
  34. package/nvim/plugins/mini.nvim/Makefile +51 -0
  35. package/nvim/plugins/mini.nvim/README.md +261 -0
  36. package/nvim/plugins/mini.nvim/TESTING.md +1011 -0
  37. package/nvim/plugins/mini.nvim/benchmarks/starter/README.md +35 -0
  38. package/nvim/plugins/mini.nvim/benchmarks/starter/benchmark.sh +56 -0
  39. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-alpha.lua +21 -0
  40. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-original.lua +17 -0
  41. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_dashboard-starter.lua +18 -0
  42. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_empty.lua +4 -0
  43. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_starter-default.lua +7 -0
  44. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-alpha.lua +10 -0
  45. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-original.lua +7 -0
  46. package/nvim/plugins/mini.nvim/benchmarks/starter/init-files/init_startify-starter.lua +20 -0
  47. package/nvim/plugins/mini.nvim/benchmarks/starter/install.sh +10 -0
  48. package/nvim/plugins/mini.nvim/benchmarks/starter/make_summary.py +68 -0
  49. package/nvim/plugins/mini.nvim/benchmarks/starter/startup-summary.md +10 -0
  50. package/nvim/plugins/mini.nvim/colors/miniautumn.lua +55 -0
  51. package/nvim/plugins/mini.nvim/colors/minicyan.lua +95 -0
  52. package/nvim/plugins/mini.nvim/colors/minischeme.lua +95 -0
  53. package/nvim/plugins/mini.nvim/colors/minispring.lua +53 -0
  54. package/nvim/plugins/mini.nvim/colors/minisummer.lua +49 -0
  55. package/nvim/plugins/mini.nvim/colors/miniwinter.lua +51 -0
  56. package/nvim/plugins/mini.nvim/colors/randomhue.lua +16 -0
  57. package/nvim/plugins/mini.nvim/doc/mini-ai.txt +889 -0
  58. package/nvim/plugins/mini.nvim/doc/mini-align.txt +923 -0
  59. package/nvim/plugins/mini.nvim/doc/mini-animate.txt +937 -0
  60. package/nvim/plugins/mini.nvim/doc/mini-base16.txt +285 -0
  61. package/nvim/plugins/mini.nvim/doc/mini-basics.txt +359 -0
  62. package/nvim/plugins/mini.nvim/doc/mini-bracketed.txt +622 -0
  63. package/nvim/plugins/mini.nvim/doc/mini-bufremove.txt +123 -0
  64. package/nvim/plugins/mini.nvim/doc/mini-clue.txt +760 -0
  65. package/nvim/plugins/mini.nvim/doc/mini-cmdline.txt +392 -0
  66. package/nvim/plugins/mini.nvim/doc/mini-colors.txt +880 -0
  67. package/nvim/plugins/mini.nvim/doc/mini-comment.txt +230 -0
  68. package/nvim/plugins/mini.nvim/doc/mini-completion.txt +477 -0
  69. package/nvim/plugins/mini.nvim/doc/mini-cursorword.txt +105 -0
  70. package/nvim/plugins/mini.nvim/doc/mini-deps.txt +590 -0
  71. package/nvim/plugins/mini.nvim/doc/mini-diff.txt +661 -0
  72. package/nvim/plugins/mini.nvim/doc/mini-doc.txt +434 -0
  73. package/nvim/plugins/mini.nvim/doc/mini-extra.txt +856 -0
  74. package/nvim/plugins/mini.nvim/doc/mini-files.txt +1018 -0
  75. package/nvim/plugins/mini.nvim/doc/mini-fuzzy.txt +160 -0
  76. package/nvim/plugins/mini.nvim/doc/mini-git.txt +425 -0
  77. package/nvim/plugins/mini.nvim/doc/mini-hipatterns.txt +489 -0
  78. package/nvim/plugins/mini.nvim/doc/mini-hues.txt +420 -0
  79. package/nvim/plugins/mini.nvim/doc/mini-icons.txt +508 -0
  80. package/nvim/plugins/mini.nvim/doc/mini-indentscope.txt +485 -0
  81. package/nvim/plugins/mini.nvim/doc/mini-input.txt +932 -0
  82. package/nvim/plugins/mini.nvim/doc/mini-jump.txt +202 -0
  83. package/nvim/plugins/mini.nvim/doc/mini-jump2d.txt +525 -0
  84. package/nvim/plugins/mini.nvim/doc/mini-keymap.txt +449 -0
  85. package/nvim/plugins/mini.nvim/doc/mini-map.txt +717 -0
  86. package/nvim/plugins/mini.nvim/doc/mini-misc.txt +431 -0
  87. package/nvim/plugins/mini.nvim/doc/mini-move.txt +181 -0
  88. package/nvim/plugins/mini.nvim/doc/mini-notify.txt +434 -0
  89. package/nvim/plugins/mini.nvim/doc/mini-nvim.txt +431 -0
  90. package/nvim/plugins/mini.nvim/doc/mini-operators.txt +472 -0
  91. package/nvim/plugins/mini.nvim/doc/mini-pairs.txt +346 -0
  92. package/nvim/plugins/mini.nvim/doc/mini-pick.txt +1535 -0
  93. package/nvim/plugins/mini.nvim/doc/mini-sessions.txt +239 -0
  94. package/nvim/plugins/mini.nvim/doc/mini-snippets.txt +1291 -0
  95. package/nvim/plugins/mini.nvim/doc/mini-splitjoin.txt +536 -0
  96. package/nvim/plugins/mini.nvim/doc/mini-starter.txt +618 -0
  97. package/nvim/plugins/mini.nvim/doc/mini-statusline.txt +361 -0
  98. package/nvim/plugins/mini.nvim/doc/mini-surround.txt +863 -0
  99. package/nvim/plugins/mini.nvim/doc/mini-tabline.txt +151 -0
  100. package/nvim/plugins/mini.nvim/doc/mini-test.txt +966 -0
  101. package/nvim/plugins/mini.nvim/doc/mini-trailspace.txt +91 -0
  102. package/nvim/plugins/mini.nvim/doc/mini-visits.txt +937 -0
  103. package/nvim/plugins/mini.nvim/logo.png +0 -0
  104. package/nvim/plugins/mini.nvim/lua/mini/ai.lua +2244 -0
  105. package/nvim/plugins/mini.nvim/lua/mini/align.lua +2076 -0
  106. package/nvim/plugins/mini.nvim/lua/mini/animate.lua +2127 -0
  107. package/nvim/plugins/mini.nvim/lua/mini/base16.lua +1752 -0
  108. package/nvim/plugins/mini.nvim/lua/mini/basics.lua +772 -0
  109. package/nvim/plugins/mini.nvim/lua/mini/bracketed.lua +2013 -0
  110. package/nvim/plugins/mini.nvim/lua/mini/bufremove.lua +283 -0
  111. package/nvim/plugins/mini.nvim/lua/mini/clue.lua +2096 -0
  112. package/nvim/plugins/mini.nvim/lua/mini/cmdline.lua +1182 -0
  113. package/nvim/plugins/mini.nvim/lua/mini/colors.lua +2483 -0
  114. package/nvim/plugins/mini.nvim/lua/mini/comment.lua +586 -0
  115. package/nvim/plugins/mini.nvim/lua/mini/completion.lua +2094 -0
  116. package/nvim/plugins/mini.nvim/lua/mini/cursorword.lua +311 -0
  117. package/nvim/plugins/mini.nvim/lua/mini/deps.lua +1660 -0
  118. package/nvim/plugins/mini.nvim/lua/mini/diff.lua +1938 -0
  119. package/nvim/plugins/mini.nvim/lua/mini/doc.lua +1371 -0
  120. package/nvim/plugins/mini.nvim/lua/mini/extra.lua +2290 -0
  121. package/nvim/plugins/mini.nvim/lua/mini/files.lua +3145 -0
  122. package/nvim/plugins/mini.nvim/lua/mini/fuzzy.lua +356 -0
  123. package/nvim/plugins/mini.nvim/lua/mini/git.lua +1737 -0
  124. package/nvim/plugins/mini.nvim/lua/mini/hipatterns.lua +1042 -0
  125. package/nvim/plugins/mini.nvim/lua/mini/hues.lua +2088 -0
  126. package/nvim/plugins/mini.nvim/lua/mini/icons.lua +2254 -0
  127. package/nvim/plugins/mini.nvim/lua/mini/indentscope.lua +1158 -0
  128. package/nvim/plugins/mini.nvim/lua/mini/init.lua +427 -0
  129. package/nvim/plugins/mini.nvim/lua/mini/input.lua +2183 -0
  130. package/nvim/plugins/mini.nvim/lua/mini/jump.lua +609 -0
  131. package/nvim/plugins/mini.nvim/lua/mini/jump2d.lua +1244 -0
  132. package/nvim/plugins/mini.nvim/lua/mini/keymap.lua +880 -0
  133. package/nvim/plugins/mini.nvim/lua/mini/map.lua +1709 -0
  134. package/nvim/plugins/mini.nvim/lua/mini/misc.lua +969 -0
  135. package/nvim/plugins/mini.nvim/lua/mini/move.lua +509 -0
  136. package/nvim/plugins/mini.nvim/lua/mini/notify.lua +938 -0
  137. package/nvim/plugins/mini.nvim/lua/mini/operators.lua +1349 -0
  138. package/nvim/plugins/mini.nvim/lua/mini/pairs.lua +657 -0
  139. package/nvim/plugins/mini.nvim/lua/mini/pick.lua +3876 -0
  140. package/nvim/plugins/mini.nvim/lua/mini/sessions.lua +689 -0
  141. package/nvim/plugins/mini.nvim/lua/mini/snippets.lua +2828 -0
  142. package/nvim/plugins/mini.nvim/lua/mini/splitjoin.lua +1132 -0
  143. package/nvim/plugins/mini.nvim/lua/mini/starter.lua +1599 -0
  144. package/nvim/plugins/mini.nvim/lua/mini/statusline.lua +686 -0
  145. package/nvim/plugins/mini.nvim/lua/mini/surround.lua +2388 -0
  146. package/nvim/plugins/mini.nvim/lua/mini/tabline.lua +564 -0
  147. package/nvim/plugins/mini.nvim/lua/mini/test.lua +2516 -0
  148. package/nvim/plugins/mini.nvim/lua/mini/trailspace.lua +215 -0
  149. package/nvim/plugins/mini.nvim/lua/mini/visits.lua +1577 -0
  150. package/nvim/plugins/mini.nvim/readmes/mini-ai.md +190 -0
  151. package/nvim/plugins/mini.nvim/readmes/mini-align.md +203 -0
  152. package/nvim/plugins/mini.nvim/readmes/mini-animate.md +214 -0
  153. package/nvim/plugins/mini.nvim/readmes/mini-base16.md +198 -0
  154. package/nvim/plugins/mini.nvim/readmes/mini-basics.md +184 -0
  155. package/nvim/plugins/mini.nvim/readmes/mini-bracketed.md +193 -0
  156. package/nvim/plugins/mini.nvim/readmes/mini-bufremove.md +139 -0
  157. package/nvim/plugins/mini.nvim/readmes/mini-clue.md +260 -0
  158. package/nvim/plugins/mini.nvim/readmes/mini-cmdline.md +183 -0
  159. package/nvim/plugins/mini.nvim/readmes/mini-colors.md +169 -0
  160. package/nvim/plugins/mini.nvim/readmes/mini-comment.md +183 -0
  161. package/nvim/plugins/mini.nvim/readmes/mini-completion.md +217 -0
  162. package/nvim/plugins/mini.nvim/readmes/mini-cursorword.md +137 -0
  163. package/nvim/plugins/mini.nvim/readmes/mini-deps.md +271 -0
  164. package/nvim/plugins/mini.nvim/readmes/mini-diff.md +289 -0
  165. package/nvim/plugins/mini.nvim/readmes/mini-doc.md +198 -0
  166. package/nvim/plugins/mini.nvim/readmes/mini-extra.md +158 -0
  167. package/nvim/plugins/mini.nvim/readmes/mini-files.md +247 -0
  168. package/nvim/plugins/mini.nvim/readmes/mini-fuzzy.md +134 -0
  169. package/nvim/plugins/mini.nvim/readmes/mini-git.md +168 -0
  170. package/nvim/plugins/mini.nvim/readmes/mini-hipatterns.md +176 -0
  171. package/nvim/plugins/mini.nvim/readmes/mini-hues.md +285 -0
  172. package/nvim/plugins/mini.nvim/readmes/mini-icons.md +183 -0
  173. package/nvim/plugins/mini.nvim/readmes/mini-indentscope.md +191 -0
  174. package/nvim/plugins/mini.nvim/readmes/mini-input.md +163 -0
  175. package/nvim/plugins/mini.nvim/readmes/mini-jump.md +161 -0
  176. package/nvim/plugins/mini.nvim/readmes/mini-jump2d.md +197 -0
  177. package/nvim/plugins/mini.nvim/readmes/mini-keymap.md +205 -0
  178. package/nvim/plugins/mini.nvim/readmes/mini-map.md +191 -0
  179. package/nvim/plugins/mini.nvim/readmes/mini-misc.md +145 -0
  180. package/nvim/plugins/mini.nvim/readmes/mini-move.md +163 -0
  181. package/nvim/plugins/mini.nvim/readmes/mini-notify.md +175 -0
  182. package/nvim/plugins/mini.nvim/readmes/mini-operators.md +193 -0
  183. package/nvim/plugins/mini.nvim/readmes/mini-pairs.md +158 -0
  184. package/nvim/plugins/mini.nvim/readmes/mini-pick.md +340 -0
  185. package/nvim/plugins/mini.nvim/readmes/mini-sessions.md +164 -0
  186. package/nvim/plugins/mini.nvim/readmes/mini-snippets.md +373 -0
  187. package/nvim/plugins/mini.nvim/readmes/mini-splitjoin.md +194 -0
  188. package/nvim/plugins/mini.nvim/readmes/mini-starter.md +176 -0
  189. package/nvim/plugins/mini.nvim/readmes/mini-statusline.md +155 -0
  190. package/nvim/plugins/mini.nvim/readmes/mini-surround.md +190 -0
  191. package/nvim/plugins/mini.nvim/readmes/mini-tabline.md +156 -0
  192. package/nvim/plugins/mini.nvim/readmes/mini-test.md +175 -0
  193. package/nvim/plugins/mini.nvim/readmes/mini-trailspace.md +137 -0
  194. package/nvim/plugins/mini.nvim/readmes/mini-visits.md +243 -0
  195. package/nvim/plugins/mini.nvim/scripts/dual_log.sh +13 -0
  196. package/nvim/plugins/mini.nvim/scripts/dual_push.sh +15 -0
  197. package/nvim/plugins/mini.nvim/scripts/dual_release.sh +102 -0
  198. package/nvim/plugins/mini.nvim/scripts/dual_sync.sh +115 -0
  199. package/nvim/plugins/mini.nvim/scripts/init-deps-example.lua +59 -0
  200. package/nvim/plugins/mini.nvim/scripts/lint-filename-length.sh +16 -0
  201. package/nvim/plugins/mini.nvim/scripts/lintcommit-ci.sh +18 -0
  202. package/nvim/plugins/mini.nvim/scripts/lintcommit.lua +393 -0
  203. package/nvim/plugins/mini.nvim/scripts/minidoc.lua +57 -0
  204. package/nvim/plugins/mini.nvim/scripts/minimal_init.lua +28 -0
  205. package/nvim/plugins/mini.nvim/scripts/minitest.lua +4 -0
  206. package/package.json +16 -0
  207. package/pyproject.toml +43 -0
  208. package/yazi/init.lua +12 -0
  209. package/yazi/keymap.toml +114 -0
  210. package/yazi/lesskey +5 -0
  211. package/yazi/plugins/bookmarks.yazi/main.lua +86 -0
  212. package/yazi/plugins/cd-git-root.yazi/main.lua +31 -0
  213. package/yazi/plugins/smart-enter.yazi/main.lua +11 -0
  214. package/yazi/theme.toml +26 -0
  215. package/yazi/yazi.toml +15 -0
  216. package/zsh/.zshrc +145 -0
  217. package/zsh/starship.toml +20 -0
  218. package/zsh/zsh-patina.toml +2 -0
@@ -0,0 +1,1011 @@
1
+ # How to test with 'mini.test'
2
+
3
+ Writing tests for Neovim Lua plugin is hard. Writing good tests for Neovim Lua plugin is even harder. The 'mini.test' module is designed to make it reasonably easier while still allowing lots of flexibility. It deliberately favors a more verbose and program-like style of writing tests, opposite to "human readable, DSL like" approach of [nvim-lua/plenary.nvim](https://github.com/nvim-lua/plenary.nvim) ("busted-style testing" from [Olivine-Labs/busted](https://github.com/Olivine-Labs/busted)). Although the latter is also possible.
4
+
5
+ This file is intended as a hands-on introduction to 'mini.test' with examples. For more details, see [its documentation](doc/mini-test.txt) and tests of this plugin's modules.
6
+
7
+ General approach of writing test files:
8
+
9
+ - Organize tests in separate Lua files.
10
+ - Each file should be associated with a test set table (output of `MiniTest.new_set()`). Recommended approach is to create it manually in each test file and then return it.
11
+ - Each test action should be defined in separate function assign to an entry of test set.
12
+ - It is strongly encouraged to use custom Neovim processes to do actual testing inside test action. See [Using child process](#using-child-process).
13
+
14
+ **NOTES**:
15
+
16
+ - All commands are assumed to be executed with current working directory being a root of your Neovim plugin project. That is both for shell and Neovim commands.
17
+ - All paths are assumed to be relative to current working directory.
18
+
19
+ ## Example plugin
20
+
21
+ In this file we will be testing 'hello_lines' plugin (once some basic concepts are introduced). It will have functionality to add prefix 'Hello ' to lines implemented in a single file 'lua/hello_lines/init.lua':
22
+
23
+ <details><summary>'lua/hello_lines/init.lua'</summary>
24
+
25
+ ```lua
26
+ local M = {}
27
+
28
+ --- Prepend 'Hello ' to every element
29
+ ---@param lines table Array. Default: { 'world' }.
30
+ ---@return table Array of strings.
31
+ M.compute = function(lines)
32
+ lines = lines or { 'world' }
33
+ return vim.tbl_map(function(x) return 'Hello ' .. tostring(x) end, lines)
34
+ end
35
+
36
+ local ns_id = vim.api.nvim_create_namespace('hello_lines')
37
+
38
+ --- Set lines with highlighted 'Hello ' prefix
39
+ ---@param buf_id number Buffer handle where lines should be set. Default: 0.
40
+ ---@param lines table Array. Default: { 'world' }.
41
+ M.set_lines = function(buf_id, lines)
42
+ buf_id = buf_id or 0
43
+ lines = lines or { 'world' }
44
+ vim.api.nvim_buf_clear_namespace(buf_id, ns_id, 0, -1)
45
+ vim.api.nvim_buf_set_lines(buf_id or 0, 0, -1, true, M.compute(lines))
46
+ for i = 1, #lines do
47
+ local extmark_opts = { end_row = i - 1, end_col = 5, hl_group = 'Special' }
48
+ vim.api.nvim_buf_set_extmark(buf_id, ns_id, i - 1, 0, extmark_opts)
49
+ end
50
+ end
51
+
52
+ return M
53
+ ```
54
+
55
+ </details>
56
+
57
+ ## Quick demo
58
+
59
+ Here is a quick demo of how tests with 'mini.test' look like:
60
+
61
+ <details><summary>'tests/test_hello_lines.lua'</summary>
62
+
63
+ ```lua
64
+ -- Define helper aliases
65
+ local new_set = MiniTest.new_set
66
+ local expect, eq = MiniTest.expect, MiniTest.expect.equality
67
+
68
+ -- Create (but not start) child Neovim object
69
+ local child = MiniTest.new_child_neovim()
70
+
71
+ -- Define main test set of this file
72
+ local T = new_set({
73
+ -- Register hooks
74
+ hooks = {
75
+ -- This will be executed before every (even nested) case
76
+ pre_case = function()
77
+ -- Restart child process with custom 'init.lua' script
78
+ child.restart({ '-u', 'scripts/minimal_init.lua' })
79
+ -- Load tested plugin
80
+ child.lua([[M = require('hello_lines')]])
81
+ end,
82
+ -- This will be executed one after all tests from this set are finished
83
+ post_once = child.stop,
84
+ },
85
+ })
86
+
87
+ -- Test set fields define nested structure
88
+ T['compute()'] = new_set()
89
+
90
+ -- Define test action as callable field of test set.
91
+ -- If it produces error - test fails.
92
+ T['compute()']['works'] = function()
93
+ -- Execute Lua code inside child process, get its result and compare with
94
+ -- expected result
95
+ eq(child.lua_get([[M.compute({'a', 'b'})]]), { 'Hello a', 'Hello b' })
96
+ end
97
+
98
+ T['compute()']['uses correct defaults'] = function()
99
+ eq(child.lua_get([[M.compute()]]), { 'Hello world' })
100
+ end
101
+
102
+ -- Make parametrized tests. This will create three copies of each case
103
+ T['set_lines()'] = new_set({ parametrize = { {}, { 0, { 'a' } }, { 0, { 1, 2, 3 } } } })
104
+
105
+ -- Use arguments from test parametrization
106
+ T['set_lines()']['works'] = function(buf_id, lines)
107
+ -- Directly modify some options to make better test
108
+ child.o.lines, child.o.columns = 10, 20
109
+ child.bo.readonly = false
110
+
111
+ -- Execute Lua code without returning value
112
+ child.lua('M.set_lines(...)', { buf_id, lines })
113
+
114
+ -- Test screen state. On first run it will automatically create reference
115
+ -- screenshots with text and look information in predefined location. On
116
+ -- later runs it will compare current screenshot with reference. Will throw
117
+ -- informative error with helpful information if they don't match exactly.
118
+ expect.reference_screenshot(child.get_screenshot())
119
+ end
120
+
121
+ -- Return test set which will be collected and execute inside `MiniTest.run()`
122
+ return T
123
+ ```
124
+
125
+ </details>
126
+
127
+ ## File organization
128
+
129
+ It might be a bit overwhelming. It actually is for most of the people. However, it should be done once and then you rarely need to touch it.
130
+
131
+ Overview of full file structure used in for testing 'hello_lines' plugin:
132
+
133
+ ```
134
+ .
135
+ ├── deps
136
+ │ └── mini.nvim # Mandatory
137
+ ├── lua
138
+ │ └── hello_lines
139
+ │ └── init.lua # Mandatory
140
+ ├── Makefile # Recommended
141
+ ├── scripts
142
+ │ ├── minimal_init.lua # Mandatory
143
+ │ └── minitest.lua # Recommended
144
+ └── tests
145
+ └── test_hello_lines.lua # Mandatory
146
+ ```
147
+
148
+ To write tests, you'll need these files:
149
+
150
+ Mandatory:
151
+
152
+ - **Your Lua plugin in 'lua' directory**. Here we will be testing 'hello_lines' plugin.
153
+
154
+ - **Test files**. By default they should be Lua files located in 'tests/' directory and named with 'test_' prefix. For example, we will write everything in 'test_hello_lines.lua'. It is usually a good idea to follow this template (will be assumed for the rest of this file):
155
+
156
+ <details><summary>Template for test files</summary>
157
+
158
+ ```lua
159
+ local new_set = MiniTest.new_set
160
+ local expect, eq = MiniTest.expect, MiniTest.expect.equality
161
+
162
+ local T = new_set()
163
+
164
+ -- Actual tests definitions will go here
165
+
166
+ return T
167
+ ```
168
+
169
+ </details>
170
+
171
+ - **'mini.nvim' dependency**. It is needed to use its 'mini.test' module. Proposed way to store it is in 'deps/mini.nvim' directory. Create it with `git`:
172
+
173
+ ```bash
174
+ mkdir -p deps
175
+ git clone --filter=blob:none https://github.com/nvim-mini/mini.nvim deps/mini.nvim
176
+ ```
177
+
178
+ - **Manual Neovim startup file** (a.k.a 'init.lua') with proposed path 'scripts/minimal_init.lua'. It will be used to ensure that Neovim processes can recognize your tested plugin and 'mini.nvim' dependency. Proposed minimal content:
179
+
180
+ <details><summary>'scripts/minimal_init.lua'</summary>
181
+
182
+ ```lua
183
+ -- Add current directory to 'runtimepath' to be able to use 'lua' files
184
+ vim.cmd([[let &rtp.=','.getcwd()]])
185
+
186
+ -- Set up 'mini.test' only when calling headless Neovim (like with `make test`)
187
+ if #vim.api.nvim_list_uis() == 0 then
188
+ -- Add 'mini.nvim' to 'runtimepath' to be able to use 'mini.test'
189
+ -- Assumed that 'mini.nvim' is stored in 'deps/mini.nvim'
190
+ vim.cmd('set rtp+=deps/mini.nvim')
191
+
192
+ -- Set up 'mini.test'
193
+ require('mini.test').setup()
194
+ end
195
+ ```
196
+
197
+ </details>
198
+
199
+ Recommended:
200
+
201
+ - **Makefile**. In order to simplify running tests from shell and inside Continuous Integration services (like Github Actions), it is recommended to define Makefile. It will define steps for running tests. Proposed template:
202
+
203
+ <details><summary>Template for Makefile</summary>
204
+
205
+ ```
206
+ # Run all test files
207
+ test: deps/mini.nvim
208
+ nvim --headless --noplugin -u ./scripts/minimal_init.lua -c "lua MiniTest.run()"
209
+
210
+ # Run test from file at `$FILE` environment variable
211
+ test_file: deps/mini.nvim
212
+ nvim --headless --noplugin -u ./scripts/minimal_init.lua -c "lua MiniTest.run_file('$(FILE)')"
213
+
214
+ # Download 'mini.nvim' to use its 'mini.test' testing module
215
+ deps/mini.nvim:
216
+ @mkdir -p deps
217
+ git clone --filter=blob:none https://github.com/nvim-mini/mini.nvim $@
218
+ ```
219
+
220
+ </details>
221
+
222
+ - **'mini.test' script** at 'scripts/minitest.lua'. Use it to customize what is tested (which files, etc.) and how. Usually not needed, but otherwise should have some variant of a call to `MiniTest.run()`.
223
+
224
+ ## Running tests
225
+
226
+ The 'mini.test' module out of the box supports two major ways of running tests:
227
+
228
+ - **Interactive**. All test files will be run directly inside current Neovim session. This proved to be very useful for debugging while writing tests. To run tests, simply execute `:lua MiniTest.run()` / `:lua MiniTest.run_file()` / `:lua MiniTest.run_at_location()` (assuming, you already have 'mini.test' set up with `require('mini.test').setup()`). With default configuration this will result into floating window with information about results of test execution. Press `q` to close it. **Note**: Be careful though, as it might affect your current setup. To avoid this, [use child processes](#using-child-process) inside tests.
229
+ - **Headless** (from shell). Start headless Neovim process with proper startup file and execute `lua MiniTest.run()`. Assuming full file organization from previous section, this can be achieved with `make test`. This will show information about results of test execution directly in shell.
230
+
231
+ ## Basics
232
+
233
+ These sections will show some basic capabilities of 'mini.test' and how to use them. In all examples code blocks represent some whole test file (like 'tests/test_basics.lua').
234
+
235
+ ### First test
236
+
237
+ A test is defined as function assigned to a field of test set. If it throws error, test has failed. Test file should return single test set. Here is an example:
238
+
239
+ ```lua
240
+ local T = MiniTest.new_set()
241
+
242
+ T['works'] = function()
243
+ local x = 1 + 1
244
+ if x ~= 2 then
245
+ error('`x` is not equal to 2')
246
+ end
247
+ end
248
+
249
+ return T
250
+ ```
251
+
252
+ Writing `if .. error() .. end` is too tiresome. That is why 'mini.test' comes with very minimal but usually quite enough set of *expectations*: `MiniTest.expect`. They display the intended expectation between objects and will throw error with informative message if it doesn't hold. Here is a rewritten previous example:
253
+
254
+ ```lua
255
+ local T = MiniTest.new_set()
256
+
257
+ T['works'] = function()
258
+ local x = 1 + 1
259
+ MiniTest.expect.equality(x, 2)
260
+ end
261
+
262
+ return T
263
+ ```
264
+
265
+ Test sets can be nested. This will be useful in combination with [hooks](#hooks) and [parametrization](#test-parametrization):
266
+
267
+ ```lua
268
+ local T = MiniTest.new_set()
269
+
270
+ T['big scope'] = new_set()
271
+
272
+ T['big scope']['works'] = function()
273
+ local x = 1 + 1
274
+ MiniTest.expect.equality(x, 2)
275
+ end
276
+
277
+ T['big scope']['also works'] = function()
278
+ local x = 2 + 2
279
+ MiniTest.expect.equality(x, 4)
280
+ end
281
+
282
+ T['out of scope'] = function()
283
+ local x = 3 + 3
284
+ MiniTest.expect.equality(x, 6)
285
+ end
286
+
287
+ return T
288
+ ```
289
+
290
+ **NOTE**: 'mini.test' supports emulation of busted-style testing by default. So previous example can be written like this:
291
+
292
+ ```lua
293
+ describe('big scope', function()
294
+ it('works', function()
295
+ local x = 1 + 1
296
+ MiniTest.expect.equality(x, 2)
297
+ end)
298
+
299
+ it('also works', function()
300
+ local x = 2 + 2
301
+ MiniTest.expect.equality(x, 4)
302
+ end)
303
+ end)
304
+
305
+ it('out of scope', function()
306
+ local x = 3 + 3
307
+ MiniTest.expect.equality(x, 6)
308
+ end)
309
+
310
+ -- NOTE: when using this style, no test set should be returned
311
+ ```
312
+
313
+ Although this is possible, the rest of this file will use a recommended test set approach.
314
+
315
+ ### Builtin expectations
316
+
317
+ These four builtin expectations are the ones used most commonly:
318
+
319
+ ```lua
320
+ local T = MiniTest.new_set()
321
+ local expect, eq = MiniTest.expect, MiniTest.expect.equality
322
+
323
+ local x = 1 + 1
324
+
325
+ -- This is so frequently used that having short alias proved useful
326
+ T['expect.equality'] = function()
327
+ eq(x, 2)
328
+ end
329
+
330
+ T['expect.no_equality'] = function()
331
+ expect.no_equality(x, 1)
332
+ end
333
+
334
+ T['expect.error'] = function()
335
+ -- This expectation will pass because function will throw an error
336
+ expect.error(function()
337
+ if x == 2 then error('Deliberate error') end
338
+ end)
339
+ end
340
+
341
+ T['expect.no_error'] = function()
342
+ -- This expectation will pass because function will *not* throw an error
343
+ expect.no_error(function()
344
+ if x ~= 2 then error('This should not be thrown') end
345
+ end)
346
+ end
347
+
348
+ return T
349
+ ```
350
+
351
+ ### Writing custom expectation
352
+
353
+ Although you can use `if ... error() ... end` approach, there is `MiniTest.new_expectation()` to simplify this process for some repetitive expectation. Here is an example used in this plugin:
354
+
355
+ ```lua
356
+ local T = MiniTest.new_set()
357
+
358
+ local expect_match = MiniTest.new_expectation(
359
+ -- Expectation subject
360
+ 'string matching',
361
+ -- Predicate
362
+ function(str, pattern) return str:find(pattern) ~= nil end,
363
+ -- Fail context
364
+ function(str, pattern)
365
+ return string.format('Pattern: %s\nObserved string: %s', vim.inspect(pattern), str)
366
+ end
367
+ )
368
+
369
+ T['string matching'] = function()
370
+ local x = 'abcd'
371
+ -- This will pass
372
+ expect_match(x, '^a')
373
+
374
+ -- This will fail
375
+ expect_match(x, 'x')
376
+ end
377
+
378
+ return T
379
+ ```
380
+
381
+ Executing this content from file 'tests/test_basics.lua' will fail with the following message:
382
+
383
+ ```
384
+ FAIL in "tests/test_basics.lua | string matching":
385
+ Failed expectation for string matching.
386
+ Pattern: "x"
387
+ Observed string: abcd
388
+ Traceback:
389
+ tests/test_basics.lua:20
390
+ ```
391
+
392
+ ### Hooks
393
+
394
+ Hooks are functions that will be called without arguments at predefined stages of test execution. They are defined for a test set. There are four types of hooks:
395
+
396
+ - **pre_once** - executed before first (filtered) node.
397
+ - **pre_case** - executed before each case (even nested).
398
+ - **post_case** - executed after each case (even nested).
399
+ - **post_once** - executed after last (filtered) node.
400
+
401
+ Example:
402
+
403
+ ```lua
404
+ local new_set = MiniTest.new_set
405
+ local expect, eq = MiniTest.expect, MiniTest.expect.equality
406
+
407
+ local T = new_set()
408
+
409
+ local n = 0
410
+ local increase_n = function() n = n + 1 end
411
+
412
+ T['hooks'] = new_set({
413
+ hooks = { pre_once = increase_n, pre_case = increase_n, post_case = increase_n, post_once = increase_n },
414
+ })
415
+
416
+ T['hooks']['work'] = function()
417
+ -- `n` will be increased twice: in `pre_once` and `pre_case`
418
+ eq(n, 2)
419
+ end
420
+
421
+ T['hooks']['work again'] = function()
422
+ -- `n` will be increased twice: in `post_case` from previous case and
423
+ -- `pre_case` before this one
424
+ eq(n, 4)
425
+ end
426
+
427
+ T['after hooks set'] = function()
428
+ -- `n` will be again increased twice: in `post_case` from previous case and
429
+ -- `post_once` after last case in T['hooks'] test set
430
+ eq(n, 6)
431
+ end
432
+
433
+ return T
434
+ ```
435
+
436
+ ### Test parametrization
437
+
438
+ One of the distinctive features of 'mini.test' is ability to leverage test parametrization. As hooks, it is a feature of test set.
439
+
440
+ Example of simple parametrization:
441
+
442
+ ```lua
443
+ local new_set = MiniTest.new_set
444
+ local eq = MiniTest.expect.equality
445
+
446
+ local T = new_set()
447
+
448
+ -- Each parameter should be an array to allow parametrizing multiple arguments
449
+ T['parametrize'] = new_set({ parametrize = { { 1 }, { 2 } } })
450
+
451
+ -- This will result into two cases. First will fail.
452
+ T['parametrize']['works'] = function(x)
453
+ eq(x, 2)
454
+ end
455
+
456
+ -- Parametrization can be nested. Cases are "multiplied" with every combination
457
+ -- of parameters.
458
+ T['parametrize']['nested'] = new_set({ parametrize = { { '1' }, { '2' } } })
459
+
460
+ -- This will result into four cases. Two of them will fail.
461
+ T['parametrize']['nested']['works'] = function(x, y)
462
+ eq(tostring(x), y)
463
+ end
464
+
465
+ -- Parametrizing multiple arguments
466
+ T['parametrize multiple arguments'] = new_set({ parametrize = { { 1, 1 }, { 2, 2 } } })
467
+
468
+ -- This will result into two cases. Both will pass.
469
+ T['parametrize multiple arguments']['works'] = function(x, y)
470
+ eq(x, y)
471
+ end
472
+
473
+ return T
474
+ ```
475
+
476
+ ### Retry
477
+
478
+ Some tests can be inherently flaky (can randomly fail even if its tested feature is correct). For example, testing that sequence of events is executed with correct delay between each other. Such tests can work reliably on fast machines, but can spuriously fail on slow ones (like during Continuous Integrations checks) while underlying feature is correct.
479
+
480
+ To reduce flakiness, there is a feature of test set called `n_retry`: a maximum number of times to retry each its test case until success.
481
+
482
+ Example of how it can be used:
483
+
484
+ ```lua
485
+ local new_set = MiniTest.new_set
486
+
487
+ local T = new_set()
488
+
489
+ -- Each case will be attempted until first success at most 5 times
490
+ T['n_retry'] = new_set({ n_retry = 5 })
491
+
492
+ -- With default `n_retry = 1` this case will fail 1 out of 2 runs.
493
+ -- With `n_retry = 5` this case will fail 1 out of 32 runs.
494
+ T['n_retry']['case'] = function()
495
+ math.randomseed(vim.loop.hrtime())
496
+ assert(math.random() < 0.5)
497
+ end
498
+
499
+ return T
500
+ ```
501
+
502
+ ### Runtime access to current cases
503
+
504
+ There is `MiniTest.current` table containing information about "current" test cases. It has `all_cases` and `case` fields with all currently executed tests and *the* current case.
505
+
506
+ Test case is a single unit of sequential test execution. It contains all information needed to execute test case along with data about its execution. Example:
507
+
508
+ ```lua
509
+ local new_set = MiniTest.new_set
510
+ local eq = MiniTest.expect.equality
511
+
512
+ local T = new_set()
513
+
514
+ T['MiniTest.current.all_cases'] = function()
515
+ -- A useful hack: show runtime data with expecting it to be something else
516
+ eq(MiniTest.current.all_cases, 0)
517
+ end
518
+
519
+ T['MiniTest.current.case'] = function()
520
+ eq(MiniTest.current.case, 0)
521
+ end
522
+
523
+ return T
524
+ ```
525
+
526
+ This will result into following lengthy fails:
527
+
528
+ <details><summary>Fail information</summary>
529
+
530
+ ```
531
+ FAIL in "tests/test_basics.lua | MiniTest.current.all_cases":
532
+ Failed expectation for equality.
533
+ Left: { {
534
+ args = {},
535
+ data = {},
536
+ desc = { "tests/test_basics.lua", "MiniTest.current.all_cases" },
537
+ exec = {
538
+ fails = {},
539
+ notes = {},
540
+ state = "Executing test"
541
+ },
542
+ hooks = {
543
+ post = {},
544
+ pre = {}
545
+ },
546
+ test = <function 1>
547
+ }, {
548
+ args = {},
549
+ data = {},
550
+ desc = { "tests/test_basics.lua", "MiniTest.current.case" },
551
+ hooks = {
552
+ post = {},
553
+ pre = {}
554
+ },
555
+ test = <function 2>
556
+ } }
557
+ Right: 0
558
+ Traceback:
559
+ tests/test_basics.lua:8
560
+
561
+ FAIL in "tests/test_basics.lua | MiniTest.current.case":
562
+ Failed expectation for equality.
563
+ Left: {
564
+ args = {},
565
+ data = {},
566
+ desc = { "tests/test_basics.lua", "MiniTest.current.case" },
567
+ exec = {
568
+ fails = {},
569
+ notes = {},
570
+ state = "Executing test"
571
+ },
572
+ hooks = {
573
+ post = {},
574
+ pre = {}
575
+ },
576
+ test = <function 1>
577
+ }
578
+ Right: 0
579
+ Traceback:
580
+ tests/test_basics.lua:12
581
+ ```
582
+
583
+ </details>
584
+
585
+ ### Case helpers
586
+
587
+ There are some functions intended to help writing more robust cases: `skip()`, `finally()`, and `add_note()`. The `MiniTest.current` table contains useful information about the current state of tests execution.
588
+
589
+ Example:
590
+
591
+ ```lua
592
+ local T = MiniTest.new_set()
593
+
594
+ -- `MiniTest.skip()` allows skipping rest of test execution while giving an
595
+ -- informative note. This test will pass with notes.
596
+ T['skip()'] = function()
597
+ if 1 + 1 == 2 then
598
+ MiniTest.skip('Apparently, 1 + 1 is 2')
599
+ end
600
+ error('1 + 1 is not 2')
601
+ end
602
+
603
+ -- `MiniTest.add_note()` allows adding notes. Final state will have
604
+ -- "with notes" suffix.
605
+ T['add_note()'] = function()
606
+ MiniTest.add_note('This test is not important.')
607
+ error('Custom error.')
608
+ end
609
+
610
+ -- `MiniTest.finally()` allows registering some function to be executed after
611
+ -- this case is finished executing (with or without an error).
612
+ T['finally()'] = function()
613
+ -- Add note only if test fails
614
+ MiniTest.finally(function()
615
+ if #MiniTest.current.case.exec.fails > 0 then
616
+ MiniTest.add_note('This test is flaky.')
617
+ end
618
+ end)
619
+ error('Expected error from time to time')
620
+ end
621
+
622
+ return T
623
+ ```
624
+
625
+ This will result into following messages:
626
+
627
+ ```
628
+ NOTE in "tests/test_basics.lua | skip()": Apparently, 1 + 1 is 2
629
+
630
+ FAIL in "tests/test_basics.lua | add_note()": tests/test_basics.lua:16: Custom error.
631
+ NOTE in "tests/test_basics.lua | add_note()": This test is not important.
632
+
633
+ FAIL in "tests/test_basics.lua | finally()": tests/test_basics.lua:28: Expected error from time to time
634
+ NOTE in "tests/test_basics.lua | finally()": This test is flaky.
635
+ ```
636
+
637
+ ## Customizing test run
638
+
639
+ Test run consists from two stages:
640
+
641
+ - **Collection**. It will source each appropriate file (customizable), combine all test sets into single test set, convert it from hierarchical to sequential form (array of test cases), and filter cases based on customizable predicate.
642
+ - **Execution**. It will safely execute array of test cases (with each pre-hooks, test action, post-hooks) one after another in scheduled asynchronous fashion while collecting information about how it went and calling customizable reporter methods.
643
+
644
+ All configuration goes into `opts` argument of `MiniTest.run()`.
645
+
646
+ ### Collection: custom files and filter
647
+
648
+ You can customize which files will be sourced and which cases will be later executed. Example:
649
+
650
+ ```lua
651
+ local new_set = MiniTest.new_set
652
+
653
+ local T = new_set()
654
+
655
+ -- Use `data` field to pass custom information for easier test management
656
+ T['fast'] = new_set({ data = { type = 'fast' } })
657
+ T['fast']['first test'] = function() end
658
+ T['fast']['second test'] = function() end
659
+
660
+ T['slow'] = new_set({ data = { type = 'slow' } })
661
+ T['slow']['first test'] = function() vim.loop.sleep(1000) end
662
+ T['slow']['second test'] = function() vim.loop.sleep(1000) end
663
+
664
+ return T
665
+ ```
666
+
667
+ You can run only this file ('tests/test_basics.lua') and only "fast" cases with this call:
668
+
669
+ ```lua
670
+ MiniTest.run({
671
+ collect = {
672
+ find_files = function() return { 'tests/test_basics.lua' } end,
673
+ filter_cases = function(case) return case.data.type == 'fast' end,
674
+ }
675
+ })
676
+ ```
677
+
678
+ ### Execution: custom reporter and stop on first error
679
+
680
+ You can customize execution of test cases with custom reporter (how test results are displayed in real time) and whether to stop execution after the first test case fail/error. Execution doesn't result into any output, instead it updates `MiniTest.current.all_cases` in place: each case gets an `exec` field with information about how its execution went.
681
+
682
+ Example of showing status summary table in the command line after everything is finished:
683
+
684
+ ```lua
685
+ local reporter = {
686
+ -- Other used methods are `start(cases)` and `update(case_num)`
687
+ finish = function()
688
+ local summary = {}
689
+ for _, c in ipairs(MiniTest.current.all_cases) do
690
+ local state = c.exec.state
691
+ summary[state] = (summary[state] or 0) + 1
692
+ end
693
+
694
+ print(vim.inspect(summary, { newline = ' ', indent = '' }))
695
+ end,
696
+ }
697
+
698
+ MiniTest.run({ execute = { reporter = reporter } })
699
+ ```
700
+
701
+ ## Using child process
702
+
703
+ Main feature of 'mini.test' which makes it different from other Lua testing frameworks is its design towards **custom usage of child Neovim process inside tests**. Ultimately, each test should be done with fresh Neovim process initialized with bare minimum setup (like allowing to load your plugin). To make this easier, there is a dedicated function `MiniTest.new_child_neovim()`. It returns an object with many useful helper methods, like for start/stop/restart, redirected execution (write code in current process, it gets executed in child one), emulating typing keys, **testing screen state**, etc.
704
+
705
+ ### Start/stop/restart
706
+
707
+ You can start/stop/restart child process associated with this child Neovim object. Current (from which testing is initiated) and child Neovim processes can "talk" to each through RPC messages (see `:h RPC`). It means you can programmatically execute code inside child process, get its output inside current process, and test if it meets your expectation. Child process is headless but fully functioning process which allows you to test things such as extmarks, floating windows, etc.
708
+
709
+ Although this approach proved to be useful and efficient, it is not ideal. Here are some limitations:
710
+ - Due to current RPC protocol implementation functions and userdata can't be used in both input and output with child process. Indicator of this issue is a `Cannot convert given lua type` error. Usual solution is to move some logic on the side of child process, like create and use global functions (note that they will be "forgotten" after next restart).
711
+ - Sometimes hanging process will occur: it stops executing without any output. Most of the time it is because Neovim process is "blocked", i.e. it waits for user input and won't return from other call. Common causes are active hit-enter-prompt (solution: increase prompt height to a bigger value) or Operator-pending mode (solution: exit it). To mitigate this experience, most helper methods will throw an error if they can deduce that immediate execution will lead to hanging state.
712
+
713
+ Here is recommended setup for managing child processes. It will make fresh Neovim process before every test case:
714
+
715
+ ```lua
716
+ local child = MiniTest.new_child_neovim()
717
+
718
+ local T = MiniTest.new_set({
719
+ hooks = {
720
+ pre_case = function()
721
+ -- Restart child process with custom 'init.lua' script
722
+ child.restart({ '-u', 'scripts/minimal_init.lua' })
723
+ -- Load tested plugin
724
+ child.lua([[M = require('hello_lines')]])
725
+ end,
726
+ -- Stop once all test cases are finished
727
+ post_once = child.stop,
728
+ },
729
+ })
730
+
731
+ -- Define some tests here
732
+
733
+ return T
734
+ ```
735
+
736
+ ### Executing Lua code
737
+
738
+ Previous section already demonstrated that there is a `child.lua()` method. It will execute arbitrary Lua code in the form of a single string. This is basically a wrapper for `vim.api.nvim_exec_lua()`. There is also a convenience wrapper `child.lua_get()` which is essentially a `child.lua('return ' .. s, ...)`. Examples:
739
+
740
+ ```lua
741
+ local eq = MiniTest.expect.equality
742
+
743
+ local child = MiniTest.new_child_neovim()
744
+
745
+ local T = MiniTest.new_set({
746
+ hooks = {
747
+ pre_case = function()
748
+ child.restart({ '-u', 'scripts/minimal_init.lua' })
749
+ child.lua([[M = require('hello_lines')]])
750
+ end,
751
+ post_once = child.stop,
752
+ },
753
+ })
754
+
755
+ T['lua()'] = MiniTest.new_set()
756
+
757
+ T['lua()']['works'] = function()
758
+ child.lua('_G.n = 0; _G.n = _G.n + 1')
759
+ eq(child.lua('return _G.n'), 1)
760
+ end
761
+
762
+ T['lua()']['can use tested plugin'] = function()
763
+ eq(child.lua('return M.compute()'), { 'Hello world' })
764
+ eq(child.lua([[return M.compute({'a', 'b'})]]), { 'Hello a', 'Hello b' })
765
+ end
766
+
767
+ T['lua_get()'] = function()
768
+ child.lua('_G.n = 0')
769
+ eq(child.lua_get('_G.n'), child.lua('return _G.n'))
770
+ end
771
+
772
+ return T
773
+ ```
774
+
775
+ ### Managing Neovim options and state
776
+
777
+ Although ability to execute arbitrary Lua code is technically enough to write any tests, it gets cumbersome very quickly due it using only string input. That is why there are many convenience helpers with the same idea: write code inside current Neovim process that will be automatically executed same way in child process. Here is the showcase:
778
+
779
+ ```lua
780
+ local new_set = MiniTest.new_set
781
+ local eq = MiniTest.expect.equality
782
+
783
+ local child = MiniTest.new_child_neovim()
784
+
785
+ local T = MiniTest.new_set({
786
+ hooks = {
787
+ pre_case = function()
788
+ child.restart({ '-u', 'scripts/minimal_init.lua' })
789
+ child.lua([[M = require('hello_lines')]])
790
+ end,
791
+ post_once = child.stop,
792
+ },
793
+ })
794
+
795
+ -- These methods will "redirect" execution to child through `vim.rpcrequest()`
796
+ -- and `vim.rpcnotify()` respectively. Any call `child.api.xxx(...)` returns
797
+ -- the output of `vim.api.xxx(...)` executed inside child process.
798
+ T['api()/api_notify()'] = function()
799
+ -- Set option. For some reason, first buffer is 'readonly' which leads to
800
+ -- high delay in test execution
801
+ child.api.nvim_set_option_value('readonly', false, { buf = 0 })
802
+
803
+ -- Set all lines
804
+ child.api.nvim_buf_set_lines(0, 0, -1, true, { 'aaa' })
805
+
806
+ -- Get all lines and test with expected ones
807
+ eq(child.api.nvim_buf_get_lines(0, 0, -1, true), { 'aaa' })
808
+ end
809
+
810
+ -- Execute Vimscript with or without capturing its output
811
+ T['cmd()/cmd()'] = function()
812
+ child.cmd('hi Comment guifg=#aaaaaa')
813
+ eq(child.cmd_capture('hi Comment'), 'Comment xxx guifg=#aaaaaa')
814
+ end
815
+
816
+ -- There are redirection tables for most of the main Neovim functionality
817
+ T['various redirection tables with methods'] = function()
818
+ eq(child.fn.fnamemodify('hello_lines.lua', ':t:r'), 'hello_lines')
819
+ eq(child.loop.hrtime() > 0, true)
820
+ eq(child.lsp.get_clients(), {})
821
+
822
+ -- And more
823
+ end
824
+
825
+ -- There are redirection tables for scoped (buffer, window, etc.) variables
826
+ -- You can use them to both set and get values
827
+ T['redirection tables for variables'] = function()
828
+ child.b.aaa = true
829
+ eq(child.b.aaa, true)
830
+ eq(child.b.aaa, child.lua_get('vim.b.aaa'))
831
+ end
832
+
833
+ -- There are redirection tables for scoped (buffer, window, etc.) options
834
+ -- You can use them to both set and get values
835
+ T['redirection tables for options'] = function()
836
+ child.o.lines, child.o.columns = 5, 12
837
+ eq(child.o.lines, 5)
838
+ eq({ child.o.lines, child.o.columns }, child.lua_get('{ vim.o.lines, vim.o.columns }'))
839
+ end
840
+
841
+ return T
842
+ ```
843
+
844
+ ### Emulate typing keys
845
+
846
+ Very important part of testing is emulating user typing keys. There is a special `child.type_keys()` helper method for that. Examples:
847
+
848
+ ```lua
849
+ local eq = MiniTest.expect.equality
850
+
851
+ local child = MiniTest.new_child_neovim()
852
+
853
+ local T = MiniTest.new_set({
854
+ hooks = {
855
+ pre_case = function()
856
+ child.restart({ '-u', 'scripts/minimal_init.lua' })
857
+ child.bo.readonly = false
858
+ child.lua([[M = require('hello_lines')]])
859
+ end,
860
+ post_once = child.stop,
861
+ },
862
+ })
863
+
864
+ local get_lines = function() return child.api.nvim_buf_get_lines(0, 0, -1, true) end
865
+
866
+ T['type_keys()'] = MiniTest.new_set()
867
+
868
+ T['type_keys()']['works'] = function()
869
+ -- It can take one string
870
+ child.type_keys('iabcde<Esc>')
871
+ eq(get_lines(), { 'abcde' })
872
+ eq(child.fn.mode(), 'n')
873
+
874
+ -- Or several strings which improves readability
875
+ child.type_keys('cc', 'fghij', '<Esc>')
876
+ eq(get_lines(), { 'fghij' })
877
+
878
+ -- Or tables of strings (possibly nested)
879
+ child.type_keys({ 'cc', { 'j', 'k', 'l', 'm', 'n' } })
880
+ eq(get_lines(), { 'jklmn' })
881
+ end
882
+
883
+ T['type_keys()']['allows custom delay'] = function()
884
+ -- This adds delay of 500 ms after each supplied string (three times here)
885
+ child.type_keys(500, 'i', 'abcde', '<Esc>')
886
+ eq(get_lines(), { 'abcde' })
887
+ end
888
+
889
+ return T
890
+ ```
891
+
892
+ ### Test screen state with screenshots
893
+
894
+ One of the main difficulties in testing Neovim plugins is verifying that something is actually displayed in the way you intend. Like general highlighting, statusline, tabline, sign column, extmarks, etc. Testing screen state with screenshots makes this a lot easier. There is a `child.get_screenshot()` method which basically calls `screenstring()` (`:h screenstring()`) and `screenattr()` (`:h screenattr()`) for every visible cell (row from 1 to 'lines' option, column from 1 to 'columns' option). It then returns screenshot with two layers:
895
+
896
+ - `text` - "2d array" (row-column) of single characters displayed at particular cells.
897
+ - `attr` - "2d array" (row-column) of symbols representing how text is displayed (basically, "coded" appearance/highlighting). They should be used only in relation to each other: same/different symbols for two cells mean same/different visual appearance. Note: there will be false positives if there are more than 94 different attribute values. To make output more portable and visually useful, outputs of `screenattr()` are coded with single character symbols.
898
+
899
+ Couple of caveats:
900
+
901
+ - As is apparent from use of `screenattr()`, these screenshots **can't tell how exactly cell is highlighted**, only **if two cells are highlighted the same**. This is due to the currently lacking functionality in Neovim itself. This might change in the future.
902
+
903
+ To help manage testing screen state, there is a special `MiniTest.expect.reference_screenshot(screenshot, path, opts)` method. It takes screenshot table along with optional path of where to save this screenshot (if not supplied, inferred from test case description and put in 'tests/screenshots' directory). On first run it will automatically create reference screenshot at `path`. On later runs it will compare current screenshot with reference. Will throw informative error with helpful information if they don't match exactly.
904
+
905
+ Example:
906
+
907
+ ```lua
908
+ local expect = MiniTest.expect
909
+
910
+ local child = MiniTest.new_child_neovim()
911
+
912
+ local T = MiniTest.new_set({
913
+ hooks = {
914
+ pre_case = function()
915
+ child.restart({ '-u', 'scripts/minimal_init.lua' })
916
+ child.bo.readonly = false
917
+ child.lua([[M = require('hello_lines')]])
918
+ end,
919
+ post_once = child.stop,
920
+ },
921
+ })
922
+
923
+ T['set_lines()'] = MiniTest.new_set({ parametrize = { {}, { 0, { 'a' } }, { 0, { 1, 2, 3 } } } })
924
+
925
+ T['set_lines()']['works'] = function(buf_id, lines)
926
+ child.o.lines, child.o.columns = 10, 15
927
+ child.lua('M.set_lines(...)', { buf_id, lines })
928
+ expect.reference_screenshot(child.get_screenshot())
929
+ end
930
+
931
+ return T
932
+ ```
933
+
934
+ This will result into three files in 'tests/screenshots' with names containing test case description along with supplied arguments. Here is example reference screenshot for `{ 0, { 1, 2, 3 } }` arguments (line numbers and ruler for columns is added as file specification to make it easier to find differences between two screenshots):
935
+
936
+ ```
937
+ --|---------|-----
938
+ 01|Hello 1
939
+ 02|Hello 2
940
+ 03|Hello 3
941
+ 04|~
942
+ 05|~
943
+ 06|~
944
+ 07|~
945
+ 08|~
946
+ 09|<e] [+] 1,1 All
947
+ 10|
948
+
949
+ --|---------|-----
950
+ 01|000001111111111
951
+ 02|000001111111111
952
+ 03|000001111111111
953
+ 04|222222222222222
954
+ 05|222222222222222
955
+ 06|222222222222222
956
+ 07|222222222222222
957
+ 08|222222222222222
958
+ 09|333333333333333
959
+ 10|444444444444444
960
+ ```
961
+
962
+ To update already existing screenshot either delete the corresponding screenshot file and rerun test case or temporarily add `{ force = true }` option to `reference_screenshot()` to force updating the screenshot file.
963
+
964
+ ## General tips
965
+
966
+ - Create a 'tests/helpers.lua' file with code that can be useful in multiple files. It can have "monkey-patched" versions of 'mini.test' functions. Example:
967
+
968
+ ```lua
969
+ local Helpers = {}
970
+
971
+ Helpers.new_child_neovim = function()
972
+ local child = MiniTest.new_child_neovim()
973
+
974
+ child.setup = function()
975
+ child.restart({'-u', 'scripts/minimal_init.lua'})
976
+ child.bo.readonly = false
977
+ child.lua([[M = require('hello_lines')]])
978
+ end
979
+
980
+ return child
981
+ end
982
+
983
+ return Helpers
984
+ ```
985
+
986
+ - Write aliases for commonly used functions at top of the file. It will make your life a little bit easier and usually will lead to more readable tests. Example:
987
+
988
+ ```lua
989
+ -- Some code setting up `child`
990
+ local set_lines = function(lines) child.api.nvim_buf_set_lines(0, 0, -1, true, lines) end
991
+ ```
992
+
993
+ - When working with automatically named screenshots, beware of the following caveats:
994
+ - Some systems are case insensitive (like usually Windows and MacOS). So having two different file names which are the same ignoring case will introduce problems for users to properly install plugin.
995
+ - Some system setups have restrictions on full path length (like 260 bytes on some Git+Windows combinations) or file name length (like 255 bytes on ext4 Windows partitions and 143 bytes on eCryptfs Linux partitions). Restriction on full path is hard to accommodate for (apart from limiting file name size to some reasonable number), but trying to not have file names longer than 143 bytes (by having shorter test case names) should be reasonable.
996
+
997
+ - To make reading strings that contain Lua code easier (for `child.lua` and `child.lua_get`), you can add the following tree-sitter capture to your personal configuration. Put it in the file 'after/queries/lua/injections.scm'. Don't forget to add `; extends` at the beginning of the file (see `:h treesitter-query-modeline-extends`):
998
+
999
+ ```query
1000
+ ; extends
1001
+ (function_call
1002
+ name: (dot_index_expression
1003
+ table: (identifier) @_table
1004
+ field: (identifier) @_field)
1005
+ arguments: (arguments
1006
+ (string
1007
+ content: (string_content) @injection.content))
1008
+ (#eq? @_table child)
1009
+ (#any-of? @_field lua lua_get)
1010
+ (#set! injection.language "lua"))
1011
+ ```