I use Vader to test Vimscript.
Use your favorite plugin manager.
- Pathogen
git clone https://github.com/junegunn/vader.vim.git ~/.vim/bundle/vader.vim
- Vundle
- Add
Bundle 'junegunn/vader.vim'
to .vimrc - Run
:BundleInstall
- Add
- NeoBundle
- Add
NeoBundle 'junegunn/vader.vim'
to .vimrc - Run
:NeoBundleInstall
- Add
- vim-plug
- Add
Plug 'junegunn/vader.vim'
to .vimrc - Run
:PlugInstall
- Add
Vader [file glob ...]
Vader! [file glob ...]
- Exit Vim after running the tests with exit status of 0 or 1
vim '+Vader!*' && echo Success || echo Failure
- If the description of
Do
orExecute
block includesFIXME
orTODO
, the block is recognized as a pending test case and does not affect the exit status.
- Exit Vim after running the tests with exit status of 0 or 1
A Vader file is a flat sequence of blocks each of which starts with the block
label, such as Execute:
, followed by the content of the block indented by 2
spaces.
- Given
- Content to fill the execution buffer
- Do
- Normal-mode keystrokes that can span multiple lines
- Execute
- Vimscript to execute
- Expect
- Expected result of the preceding Do/Execute block
- Before
- Vimscript to run before each test case
- After
- Vimscript to run after each test case
The content of a Given block is pasted into the "workbench buffer" for the
subsequent Do/Execute blocks. If filetype
parameter is given, &filetype
of
the buffer is set accordingly. It is also used to syntax-highlight the block in
.vader file.
Given [filetype] [(comment)]:
[input text]
The content of a Do block is a sequence of normal-mode keystrokes that can
freely span multiple lines. A special key can be written in its name surrounded
by angle brackets preceded by a backslash (e.g. \<Enter>
).
Do block can be followed by an optional Expect block.
Do [(comment)]:
[keystrokes]
The content of an Execute block is plain Vimscript to be executed.
Execute block can also be followed by an optional Expect block.
Execute [(comment)]:
[vimscript]
In Execute block, the following commands are provided.
- Assertions
Assert <boolean expr>, [message]
AssertEqual <expected>, <got>
AssertNotEqual <unexpected>, <got>
AssertThrows <expr>
- Other commands
Log "Message"
Save <name>[, ...]
Restore [<name>, ...]
And the path of the current .vader file can be accessed via g:vader_file
.
In addition to plain Vimscript, you can also test Ruby/Python/Perl/Lua interface with Execute block as follows:
Execute [lang] [(comment)]:
[<lang> code]
See Ruby and Python examples here.
If an Expect block follows an Execute block or a Do block, the result of the
preceding block is compared to the content of the Expect block. Comparison is
case-sensitive. filetype
parameter is used to syntax-highlight the block.
Expect [filetype] [(comment)]:
[expected output]
The content of a Before block is executed before every following Do/Execute block.
Before [(comment)]:
[vim script]
The content of an After block is executed after every following Do/Execute block.
After [(comment)]:
[vim script]
You can include other vader files using Include macro.
Include: setup.vader
# ...
Include: cleanup.vader
Any line that starts with #
, =
, -
, ~
, ^
, or *
without indentation is
considered to be a comment and simply ignored.
#################
# Typical comment
#################
Given (fixture):
================
Hello
Do (modification):
------------------
* change inner word
ciw
* to
World
Expect (result):
~~~~~~~~~~~~~~~~
World
# Test case
Execute (test assertion):
%d
Assert 1 == line('$')
setf python
AssertEqual 'python', &filetype
Given ruby (some ruby code):
def a
a = 1
end
Do (indent the block):
vip=
Expect ruby (indented block):
def a
a = 1
end
Do (indent and shift):
vip=
gv>
Expect ruby (indented and shifted):
def a
a = 1
end
When you test a plugin, it's generally a good idea to setup a testing environment that is isolated from the other plugins and settings irrelevant to the test. The simplest way to achieve this is to write a minimal .vimrc such as follows and start a clean Vim process with it.
set nocompatible
filetype off
" Assuming that plugins are installed under ~/.vim/bundle
" Dependency to vader.vim
set rtp^=~/.vim/bundle/vader.vim
" The plugin under test
set rtp^=~/.vim/bundle/vim-markdown
set rtp+=~/.vim/bundle/vim-markdown/after
" Enable loading plugins and indent files for specific file types
filetype plugin indent on
Then you can start Vim process with the configuration file and run Vader tests.
vim -u mini-vimrc +Vader*
Consider writing a script to further automate the process. You may refer to the one from easy-align.
To make your project tested on Travis CI, you need to
add .travis.yml
to your project root. For most plugins the following example
should suffice.
language: vim
before_script: |
git clone https://github.com/junegunn/vader.vim.git
script: |
vim -Nu <(cat << VIMRC
filetype off
set rtp+=vader.vim
set rtp+=.
set rtp+=after
filetype plugin indent on
VIMRC) -c 'Vader! test/*' > /dev/null
(Note that vim
is not a valid language for Travis CI. It just sets up Ruby
execution environment instead as the default.)
- Simple .travis.yml
- Advanced .travis.yml
- Multiple dependencies
- Builds Vim from source
- Build result
The keystrokes given to the feedkeys() function are consumed only after Vader finishes executing the content of the Do/Execute block. Take the following case as an example:
Do (Test feedkeys() function):
i123
\<C-O>:call feedkeys('456')\<CR>
789
Expect (Wrong!):
123456789
You may have expected 123456789
, but the result is 123789456
. Unfortunately
I have yet to find a workaround for this problem. Please let me know if you find
one.
It is reported that
CursorMoved event is not triggered inside a Do block. If you need to test a
feature that involves autocommands on CursorMoved event, you have to manually
invoke it in the middle of the block using :doautocmd
.
Do (Using doautocmd):
jjj
:doautocmd CursorMoved\<CR>
This is likely a bug of Vim itself. For some reason, search history is not correctly updated when searches are performed inside a Do block. The following test scenario fails due to this problem.
Execute (Clear search history):
for _ in range(&history)
call histdel('/', -1)
endfor
Given (Search and destroy):
I'm a street walking cheetah with a heart full of napalm
I'm a runaway son of the nuclear A-bomb
I'm a world's forgotten boy
The one who searches and destroys
Do (Searches):
/street\<CR>
/walking\<CR>
/cheetah\<CR>
/runaway\<CR>
/search\<CR>
Execute (Assertions):
Log string(map(range(1, &history), 'histget("/", - v:val)'))
AssertEqual 'runaway', histget('/', -2)
AssertEqual 'search', histget('/', -1)
The result is given as follows:
Starting Vader: 1 suite(s), 3 case(s)
Starting Vader: /Users/jg/.vim/plugged/vader.vim/search-and-destroy.vader
(1/3) [EXECUTE] Clear search history
(2/3) [ GIVEN] Search and destroy
(2/3) [ DO] Searches
(3/3) [ GIVEN] Search and destroy
(3/3) [EXECUTE] Assertions
> ['search', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
(3/3) [EXECUTE] (X) Assertion failure: 'runaway' != ''
Success/Total: 2/3
Success/Total: 2/3 (assertions: 0/1)
Elapsed time: 0.366118 sec.
MIT