diff src/testdir/test_listener.vim @ 32670:695b50472e85

Fix line endings issue
author Christian Brabandt <cb@256bit.org>
date Mon, 26 Jun 2023 13:13:12 +0200
parents 448aef880252
children
line wrap: on
line diff
--- a/src/testdir/test_listener.vim
+++ b/src/testdir/test_listener.vim
@@ -1,455 +1,455 @@
-" tests for listener_add() and listener_remove()
-
-func s:StoreList(s, e, a, l)
-  let s:start = a:s
-  let s:end = a:e
-  let s:added = a:a
-  let s:text = getline(a:s)
-  let s:list = a:l
-endfunc
-
-func s:AnotherStoreList(l)
-  let s:list2 = a:l
-endfunc
-
-func s:EvilStoreList(l)
-  let s:list3 = a:l
-  call assert_fails("call add(a:l, 'myitem')", "E742:")
-endfunc
-
-func Test_listening()
-  new
-  call setline(1, ['one', 'two'])
-  let s:list = []
-  let id = listener_add({b, s, e, a, l -> s:StoreList(s, e, a, l)})
-  call setline(1, 'one one')
-  call listener_flush()
-  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
-
-  " Undo is also a change
-  set undolevels&  " start new undo block
-  call append(2, 'two two')
-  undo
-  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
-  redraw
-  " the two changes are not merged
-  call assert_equal([{'lnum': 3, 'end': 4, 'col': 1, 'added': -1}], s:list)
-  1
-
-  " Two listeners, both get called.  Also check column.
-  call setline(1, ['one one', 'two'])
-  call listener_flush()
-  let id2 = listener_add({b, s, e, a, l -> s:AnotherStoreList(l)})
-  let s:list = []
-  let s:list2 = []
-  exe "normal $asome\<Esc>"
-  redraw
-  call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list)
-  call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list2)
-
-  " removing listener works
-  call listener_remove(id2)
-  call setline(1, ['one one', 'two'])
-  call listener_flush()
-  let s:list = []
-  let s:list2 = []
-  call setline(3, 'three')
-  redraw
-  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
-  call assert_equal([], s:list2)
-
-  " a change above a previous change without a line number change is reported
-  " together
-  call setline(1, ['one one', 'two'])
-  call listener_flush(bufnr())
-  call append(2, 'two two')
-  call setline(1, 'something')
-  call bufnr()->listener_flush()
-  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
-	\ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
-  call assert_equal(1, s:start)
-  call assert_equal(3, s:end)
-  call assert_equal(1, s:added)
-
-  " an insert just above a previous change that was the last one does not get
-  " merged
-  call setline(1, ['one one', 'two'])
-  call listener_flush()
-  let s:list = []
-  call setline(2, 'something')
-  call append(1, 'two two')
-  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
-  call listener_flush()
-  call assert_equal([{'lnum': 2, 'end': 2, 'col': 1, 'added': 1}], s:list)
-
-  " an insert above a previous change causes a flush
-  call setline(1, ['one one', 'two'])
-  call listener_flush()
-  call setline(2, 'something')
-  call append(0, 'two two')
-  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
-  call assert_equal('something', s:text)
-  call listener_flush()
-  call assert_equal([{'lnum': 1, 'end': 1, 'col': 1, 'added': 1}], s:list)
-  call assert_equal('two two', s:text)
-
-  " a delete at a previous change that was the last one does not get merged
-  call setline(1, ['one one', 'two'])
-  call listener_flush()
-  let s:list = []
-  call setline(2, 'something')
-  2del
-  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
-  call listener_flush()
-  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': -1}], s:list)
-
-  " a delete above a previous change causes a flush
-  call setline(1, ['one one', 'two'])
-  call listener_flush()
-  call setline(2, 'another')
-  1del
-  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
-  call assert_equal(2, s:start)
-  call assert_equal('another', s:text)
-  call listener_flush()
-  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': -1}], s:list)
-  call assert_equal('another', s:text)
-
-  " the "o" command first adds an empty line and then changes it
-  %del
-  call setline(1, ['one one', 'two'])
-  call listener_flush()
-  let s:list = []
-  exe "normal Gofour\<Esc>"
-  redraw
-  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
-	\ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
-
-  " Remove last listener
-  let s:list = []
-  call listener_remove(id)
-  call setline(1, 'asdfasdf')
-  redraw
-  call assert_equal([], s:list)
-
-  " Trying to change the list fails
-  let id = listener_add({b, s, e, a, l -> s:EvilStoreList(l)})
-  let s:list3 = []
-  call setline(1, 'asdfasdf')
-  redraw
-  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list3)
-
-  eval id->listener_remove()
-  bwipe!
-endfunc
-
-func s:StoreListArgs(buf, start, end, added, list)
-  let s:buf = a:buf
-  let s:start = a:start
-  let s:end = a:end
-  let s:added = a:added
-  let s:list = a:list
-endfunc
-
-func Test_listener_args()
-  new
-  call setline(1, ['one', 'two'])
-  let s:list = []
-  let id = listener_add('s:StoreListArgs')
-
-  " just one change
-  call setline(1, 'one one')
-  call listener_flush()
-  call assert_equal(bufnr(''), s:buf)
-  call assert_equal(1, s:start)
-  call assert_equal(2, s:end)
-  call assert_equal(0, s:added)
-  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
-
-  " two disconnected changes
-  call setline(1, ['one', 'two', 'three', 'four'])
-  call listener_flush()
-  call setline(1, 'one one')
-  call setline(3, 'three three')
-  call listener_flush()
-  call assert_equal(bufnr(''), s:buf)
-  call assert_equal(1, s:start)
-  call assert_equal(4, s:end)
-  call assert_equal(0, s:added)
-  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0},
-	\ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
-
-  " add and remove lines
-  call setline(1, ['one', 'two', 'three', 'four', 'five', 'six'])
-  call listener_flush()
-  call append(2, 'two two')
-  4del
-  call append(5, 'five five')
-  call listener_flush()
-  call assert_equal(bufnr(''), s:buf)
-  call assert_equal(3, s:start)
-  call assert_equal(6, s:end)
-  call assert_equal(1, s:added)
-  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
-	\ {'lnum': 4, 'end': 5, 'col': 1, 'added': -1},
-	\ {'lnum': 6, 'end': 6, 'col': 1, 'added': 1}], s:list)
-
-  " split a line then insert one, should get two disconnected change lists
-  call setline(1, 'split here')
-  call listener_flush()
-  let s:list = []
-  exe "normal 1ggwi\<CR>\<Esc>"
-  1
-  normal o
-  call assert_equal([{'lnum': 1, 'end': 2, 'col': 7, 'added': 1}], s:list)
-  call listener_flush()
-  call assert_equal([{'lnum': 2, 'end': 2, 'col': 1, 'added': 1}], s:list)
-
-  call listener_remove(id)
-  bwipe!
-
-  " Invalid arguments
-  call assert_fails('call listener_add([])', 'E921:')
-  call assert_fails('call listener_add("s:StoreListArgs", [])', 'E730:')
-  call assert_fails('call listener_flush([])', 'E730:')
-
-  call assert_fails('eval ""->listener_add()', 'E119:')
-endfunc
-
-func s:StoreBufList(buf, start, end, added, list)
-  let s:bufnr = a:buf
-  let s:list = a:list
-endfunc
-
-func Test_listening_other_buf()
-  new
-  call setline(1, ['one', 'two'])
-  let bufnr = bufnr('')
-  normal ww
-  let id = bufnr->listener_add(function('s:StoreBufList'))
-  let s:list = []
-  call setbufline(bufnr, 1, 'hello')
-  redraw
-  call assert_equal(bufnr, s:bufnr)
-  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
-
-  call listener_remove(id)
-  exe "buf " .. bufnr
-  bwipe!
-endfunc
-
-func Test_listener_garbage_collect()
-  func MyListener(x, bufnr, start, end, added, changes)
-    " NOP
-  endfunc
-
-  new
-  let id = listener_add(function('MyListener', [{}]), bufnr(''))
-  call test_garbagecollect_now()
-  " must not crash caused by invalid memory access
-  normal ia
-  call assert_true(v:true)
-
-  call listener_remove(id)
-  delfunc MyListener
-  bwipe!
-endfunc
-
-" This verifies the fix for issue #4455
-func Test_listener_caches_buffer_line()
-  new
-  inoremap <silent> <CR> <CR><Esc>O
-
-  function EchoChanges(bufnr, start, end, added, changes)
-    for l:change in a:changes
-      let text = getbufline(a:bufnr, l:change.lnum, l:change.end-1+l:change.added)
-    endfor
-  endfunction
-  let lid = listener_add("EchoChanges")
-  set autoindent
-  set cindent
-
-  call setline(1, ["{", "\tif true {}", "}"])
-  exe "normal /{}\nl"
-  call feedkeys("i\r\e", 'xt')
-  call assert_equal(["{", "\tif true {", "", "\t}", "}"], getline(1, 5))
-
-  bwipe!
-  delfunc EchoChanges
-  call listener_remove(lid)
-  iunmap <CR>
-  set nocindent
-endfunc
-
-" Verify the fix for issue #4908
-func Test_listener_undo_line_number()
-  function DoIt()
-    " NOP
-  endfunction
-  function EchoChanges(bufnr, start, end, added, changes)
-    call DoIt()
-  endfunction
-
-  new
-  let lid = listener_add("EchoChanges")
-  call setline(1, ['a', 'b', 'c'])
-  set undolevels&  " start new undo block
-  call feedkeys("ggcG\<Esc>", 'xt')
-  undo
-
-  bwipe!
-  delfunc DoIt
-  delfunc EchoChanges
-  call listener_remove(lid)
-endfunc
-
-func Test_listener_undo_delete_all()
-  new
-  call setline(1, [1, 2, 3, 4])
-  let s:changes = []
-  func s:ExtendList(bufnr, start, end, added, changes)
-    call extend(s:changes, a:changes)
-  endfunc
-  let id = listener_add('s:ExtendList')
-
-  set undolevels&  " start new undo block
-  normal! ggdG
-  undo
-  call listener_flush()
-  call assert_equal(2, s:changes->len())
-  " delete removes four lines, empty line remains
-  call assert_equal({'lnum': 1, 'end': 5, 'col': 1, 'added': -4}, s:changes[0])
-  " undo replaces empty line and adds 3 lines
-  call assert_equal({'lnum': 1, 'end': 2, 'col': 1, 'added': 3}, s:changes[1])
-
-  call listener_remove(id)
-  delfunc s:ExtendList
-  unlet s:changes
-  bwipe!
-endfunc
-
-func Test_listener_cleared_newbuf()
-  func Listener(bufnr, start, end, added, changes)
-    let g:gotCalled += 1
-  endfunc
-  new
-  " check that listening works
-  let g:gotCalled = 0
-  let lid = listener_add("Listener")
-  call feedkeys("axxx\<Esc>", 'xt')
-  call listener_flush(bufnr())
-  call assert_equal(1, g:gotCalled)
-  %bwipe!
-  let bufnr = bufnr()
-  let b:testing = 123
-  let lid = listener_add("Listener")
-  enew!
-  " check buffer is reused
-  call assert_equal(bufnr, bufnr())
-  call assert_false(exists('b:testing'))
-
-  " check that listening stops when reusing the buffer
-  let g:gotCalled = 0
-  call feedkeys("axxx\<Esc>", 'xt')
-  call listener_flush(bufnr())
-  call assert_equal(0, g:gotCalled)
-  unlet g:gotCalled
-
-  bwipe!
-  delfunc Listener
-endfunc
-
-func Test_col_after_deletion_moved_cur()
-  func Listener(bufnr, start, end, added, changes)
-    call assert_equal([#{lnum: 1, end: 2, added: 0, col: 2}], a:changes)
-  endfunc
-  new
-  call setline(1, ['foo'])
-  let lid = listener_add('Listener')
-  call feedkeys("lD", 'xt')
-  call listener_flush()
-  bwipe!
-  delfunc Listener
-endfunc
-
-func Test_remove_listener_in_callback()
-  new
-  let s:ID = listener_add('Listener')
-  func Listener(...)
-    call listener_remove(s:ID)
-    let g:listener_called = 'yes'
-  endfunc
-  call setline(1, ['foo'])
-  call feedkeys("lD", 'xt')
-  call listener_flush()
-  call assert_equal('yes', g:listener_called)
-
-  bwipe!
-  delfunc Listener
-  unlet g:listener_called
-endfunc
-
-" When multiple listeners are registered, remove one listener and verify the
-" other listener is still called
-func Test_remove_one_listener_in_callback()
-  new
-  let g:listener1_called = 0
-  let g:listener2_called = 0
-  let s:ID1 = listener_add('Listener1')
-  let s:ID2 = listener_add('Listener2')
-  func Listener1(...)
-    call listener_remove(s:ID1)
-    let g:listener1_called += 1
-  endfunc
-  func Listener2(...)
-    let g:listener2_called += 1
-  endfunc
-  call setline(1, ['foo'])
-  call feedkeys("~", 'xt')
-  call listener_flush()
-  call feedkeys("~", 'xt')
-  call listener_flush()
-  call assert_equal(1, g:listener1_called)
-  call assert_equal(2, g:listener2_called)
-
-  call listener_remove(s:ID2)
-  bwipe!
-  delfunc Listener1
-  delfunc Listener2
-  unlet g:listener1_called
-  unlet g:listener2_called
-endfunc
-
-func Test_no_change_for_empty_undo()
-  new
-  let text = ['some word here', 'second line']
-  call setline(1, text)
-  let g:entries = []
-  func Listener(bufnr, start, end, added, changes)
-    for change in a:changes
-      call add(g:entries, [change.lnum, change.end, change.added])
-    endfor
-  endfunc
-  let s:ID = listener_add('Listener')
-  let @a = "one line\ntwo line\nthree line"
-  set undolevels&  " start new undo block
-  call feedkeys('fwviw"ap', 'xt')
-  call listener_flush(bufnr())
-  " first change deletes "word", second change inserts the register
-  call assert_equal([[1, 2, 0], [1, 2, 2]], g:entries)
-  let g:entries = []
-
-  set undolevels&  " start new undo block
-  undo
-  call listener_flush(bufnr())
-  call assert_equal([[1, 4, -2]], g:entries)
-  call assert_equal(text, getline(1, 2))
-
-  call listener_remove(s:ID)
-  bwipe!
-  unlet g:entries
-  delfunc Listener
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
+" tests for listener_add() and listener_remove()
+
+func s:StoreList(s, e, a, l)
+  let s:start = a:s
+  let s:end = a:e
+  let s:added = a:a
+  let s:text = getline(a:s)
+  let s:list = a:l
+endfunc
+
+func s:AnotherStoreList(l)
+  let s:list2 = a:l
+endfunc
+
+func s:EvilStoreList(l)
+  let s:list3 = a:l
+  call assert_fails("call add(a:l, 'myitem')", "E742:")
+endfunc
+
+func Test_listening()
+  new
+  call setline(1, ['one', 'two'])
+  let s:list = []
+  let id = listener_add({b, s, e, a, l -> s:StoreList(s, e, a, l)})
+  call setline(1, 'one one')
+  call listener_flush()
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
+
+  " Undo is also a change
+  set undolevels&  " start new undo block
+  call append(2, 'two two')
+  undo
+  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
+  redraw
+  " the two changes are not merged
+  call assert_equal([{'lnum': 3, 'end': 4, 'col': 1, 'added': -1}], s:list)
+  1
+
+  " Two listeners, both get called.  Also check column.
+  call setline(1, ['one one', 'two'])
+  call listener_flush()
+  let id2 = listener_add({b, s, e, a, l -> s:AnotherStoreList(l)})
+  let s:list = []
+  let s:list2 = []
+  exe "normal $asome\<Esc>"
+  redraw
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list)
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 8, 'added': 0}], s:list2)
+
+  " removing listener works
+  call listener_remove(id2)
+  call setline(1, ['one one', 'two'])
+  call listener_flush()
+  let s:list = []
+  let s:list2 = []
+  call setline(3, 'three')
+  redraw
+  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1}], s:list)
+  call assert_equal([], s:list2)
+
+  " a change above a previous change without a line number change is reported
+  " together
+  call setline(1, ['one one', 'two'])
+  call listener_flush(bufnr())
+  call append(2, 'two two')
+  call setline(1, 'something')
+  call bufnr()->listener_flush()
+  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
+	\ {'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
+  call assert_equal(1, s:start)
+  call assert_equal(3, s:end)
+  call assert_equal(1, s:added)
+
+  " an insert just above a previous change that was the last one does not get
+  " merged
+  call setline(1, ['one one', 'two'])
+  call listener_flush()
+  let s:list = []
+  call setline(2, 'something')
+  call append(1, 'two two')
+  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+  call listener_flush()
+  call assert_equal([{'lnum': 2, 'end': 2, 'col': 1, 'added': 1}], s:list)
+
+  " an insert above a previous change causes a flush
+  call setline(1, ['one one', 'two'])
+  call listener_flush()
+  call setline(2, 'something')
+  call append(0, 'two two')
+  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+  call assert_equal('something', s:text)
+  call listener_flush()
+  call assert_equal([{'lnum': 1, 'end': 1, 'col': 1, 'added': 1}], s:list)
+  call assert_equal('two two', s:text)
+
+  " a delete at a previous change that was the last one does not get merged
+  call setline(1, ['one one', 'two'])
+  call listener_flush()
+  let s:list = []
+  call setline(2, 'something')
+  2del
+  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+  call listener_flush()
+  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': -1}], s:list)
+
+  " a delete above a previous change causes a flush
+  call setline(1, ['one one', 'two'])
+  call listener_flush()
+  call setline(2, 'another')
+  1del
+  call assert_equal([{'lnum': 2, 'end': 3, 'col': 1, 'added': 0}], s:list)
+  call assert_equal(2, s:start)
+  call assert_equal('another', s:text)
+  call listener_flush()
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': -1}], s:list)
+  call assert_equal('another', s:text)
+
+  " the "o" command first adds an empty line and then changes it
+  %del
+  call setline(1, ['one one', 'two'])
+  call listener_flush()
+  let s:list = []
+  exe "normal Gofour\<Esc>"
+  redraw
+  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
+	\ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
+
+  " Remove last listener
+  let s:list = []
+  call listener_remove(id)
+  call setline(1, 'asdfasdf')
+  redraw
+  call assert_equal([], s:list)
+
+  " Trying to change the list fails
+  let id = listener_add({b, s, e, a, l -> s:EvilStoreList(l)})
+  let s:list3 = []
+  call setline(1, 'asdfasdf')
+  redraw
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list3)
+
+  eval id->listener_remove()
+  bwipe!
+endfunc
+
+func s:StoreListArgs(buf, start, end, added, list)
+  let s:buf = a:buf
+  let s:start = a:start
+  let s:end = a:end
+  let s:added = a:added
+  let s:list = a:list
+endfunc
+
+func Test_listener_args()
+  new
+  call setline(1, ['one', 'two'])
+  let s:list = []
+  let id = listener_add('s:StoreListArgs')
+
+  " just one change
+  call setline(1, 'one one')
+  call listener_flush()
+  call assert_equal(bufnr(''), s:buf)
+  call assert_equal(1, s:start)
+  call assert_equal(2, s:end)
+  call assert_equal(0, s:added)
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
+
+  " two disconnected changes
+  call setline(1, ['one', 'two', 'three', 'four'])
+  call listener_flush()
+  call setline(1, 'one one')
+  call setline(3, 'three three')
+  call listener_flush()
+  call assert_equal(bufnr(''), s:buf)
+  call assert_equal(1, s:start)
+  call assert_equal(4, s:end)
+  call assert_equal(0, s:added)
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0},
+	\ {'lnum': 3, 'end': 4, 'col': 1, 'added': 0}], s:list)
+
+  " add and remove lines
+  call setline(1, ['one', 'two', 'three', 'four', 'five', 'six'])
+  call listener_flush()
+  call append(2, 'two two')
+  4del
+  call append(5, 'five five')
+  call listener_flush()
+  call assert_equal(bufnr(''), s:buf)
+  call assert_equal(3, s:start)
+  call assert_equal(6, s:end)
+  call assert_equal(1, s:added)
+  call assert_equal([{'lnum': 3, 'end': 3, 'col': 1, 'added': 1},
+	\ {'lnum': 4, 'end': 5, 'col': 1, 'added': -1},
+	\ {'lnum': 6, 'end': 6, 'col': 1, 'added': 1}], s:list)
+
+  " split a line then insert one, should get two disconnected change lists
+  call setline(1, 'split here')
+  call listener_flush()
+  let s:list = []
+  exe "normal 1ggwi\<CR>\<Esc>"
+  1
+  normal o
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 7, 'added': 1}], s:list)
+  call listener_flush()
+  call assert_equal([{'lnum': 2, 'end': 2, 'col': 1, 'added': 1}], s:list)
+
+  call listener_remove(id)
+  bwipe!
+
+  " Invalid arguments
+  call assert_fails('call listener_add([])', 'E921:')
+  call assert_fails('call listener_add("s:StoreListArgs", [])', 'E730:')
+  call assert_fails('call listener_flush([])', 'E730:')
+
+  call assert_fails('eval ""->listener_add()', 'E119:')
+endfunc
+
+func s:StoreBufList(buf, start, end, added, list)
+  let s:bufnr = a:buf
+  let s:list = a:list
+endfunc
+
+func Test_listening_other_buf()
+  new
+  call setline(1, ['one', 'two'])
+  let bufnr = bufnr('')
+  normal ww
+  let id = bufnr->listener_add(function('s:StoreBufList'))
+  let s:list = []
+  call setbufline(bufnr, 1, 'hello')
+  redraw
+  call assert_equal(bufnr, s:bufnr)
+  call assert_equal([{'lnum': 1, 'end': 2, 'col': 1, 'added': 0}], s:list)
+
+  call listener_remove(id)
+  exe "buf " .. bufnr
+  bwipe!
+endfunc
+
+func Test_listener_garbage_collect()
+  func MyListener(x, bufnr, start, end, added, changes)
+    " NOP
+  endfunc
+
+  new
+  let id = listener_add(function('MyListener', [{}]), bufnr(''))
+  call test_garbagecollect_now()
+  " must not crash caused by invalid memory access
+  normal ia
+  call assert_true(v:true)
+
+  call listener_remove(id)
+  delfunc MyListener
+  bwipe!
+endfunc
+
+" This verifies the fix for issue #4455
+func Test_listener_caches_buffer_line()
+  new
+  inoremap <silent> <CR> <CR><Esc>O
+
+  function EchoChanges(bufnr, start, end, added, changes)
+    for l:change in a:changes
+      let text = getbufline(a:bufnr, l:change.lnum, l:change.end-1+l:change.added)
+    endfor
+  endfunction
+  let lid = listener_add("EchoChanges")
+  set autoindent
+  set cindent
+
+  call setline(1, ["{", "\tif true {}", "}"])
+  exe "normal /{}\nl"
+  call feedkeys("i\r\e", 'xt')
+  call assert_equal(["{", "\tif true {", "", "\t}", "}"], getline(1, 5))
+
+  bwipe!
+  delfunc EchoChanges
+  call listener_remove(lid)
+  iunmap <CR>
+  set nocindent
+endfunc
+
+" Verify the fix for issue #4908
+func Test_listener_undo_line_number()
+  function DoIt()
+    " NOP
+  endfunction
+  function EchoChanges(bufnr, start, end, added, changes)
+    call DoIt()
+  endfunction
+
+  new
+  let lid = listener_add("EchoChanges")
+  call setline(1, ['a', 'b', 'c'])
+  set undolevels&  " start new undo block
+  call feedkeys("ggcG\<Esc>", 'xt')
+  undo
+
+  bwipe!
+  delfunc DoIt
+  delfunc EchoChanges
+  call listener_remove(lid)
+endfunc
+
+func Test_listener_undo_delete_all()
+  new
+  call setline(1, [1, 2, 3, 4])
+  let s:changes = []
+  func s:ExtendList(bufnr, start, end, added, changes)
+    call extend(s:changes, a:changes)
+  endfunc
+  let id = listener_add('s:ExtendList')
+
+  set undolevels&  " start new undo block
+  normal! ggdG
+  undo
+  call listener_flush()
+  call assert_equal(2, s:changes->len())
+  " delete removes four lines, empty line remains
+  call assert_equal({'lnum': 1, 'end': 5, 'col': 1, 'added': -4}, s:changes[0])
+  " undo replaces empty line and adds 3 lines
+  call assert_equal({'lnum': 1, 'end': 2, 'col': 1, 'added': 3}, s:changes[1])
+
+  call listener_remove(id)
+  delfunc s:ExtendList
+  unlet s:changes
+  bwipe!
+endfunc
+
+func Test_listener_cleared_newbuf()
+  func Listener(bufnr, start, end, added, changes)
+    let g:gotCalled += 1
+  endfunc
+  new
+  " check that listening works
+  let g:gotCalled = 0
+  let lid = listener_add("Listener")
+  call feedkeys("axxx\<Esc>", 'xt')
+  call listener_flush(bufnr())
+  call assert_equal(1, g:gotCalled)
+  %bwipe!
+  let bufnr = bufnr()
+  let b:testing = 123
+  let lid = listener_add("Listener")
+  enew!
+  " check buffer is reused
+  call assert_equal(bufnr, bufnr())
+  call assert_false(exists('b:testing'))
+
+  " check that listening stops when reusing the buffer
+  let g:gotCalled = 0
+  call feedkeys("axxx\<Esc>", 'xt')
+  call listener_flush(bufnr())
+  call assert_equal(0, g:gotCalled)
+  unlet g:gotCalled
+
+  bwipe!
+  delfunc Listener
+endfunc
+
+func Test_col_after_deletion_moved_cur()
+  func Listener(bufnr, start, end, added, changes)
+    call assert_equal([#{lnum: 1, end: 2, added: 0, col: 2}], a:changes)
+  endfunc
+  new
+  call setline(1, ['foo'])
+  let lid = listener_add('Listener')
+  call feedkeys("lD", 'xt')
+  call listener_flush()
+  bwipe!
+  delfunc Listener
+endfunc
+
+func Test_remove_listener_in_callback()
+  new
+  let s:ID = listener_add('Listener')
+  func Listener(...)
+    call listener_remove(s:ID)
+    let g:listener_called = 'yes'
+  endfunc
+  call setline(1, ['foo'])
+  call feedkeys("lD", 'xt')
+  call listener_flush()
+  call assert_equal('yes', g:listener_called)
+
+  bwipe!
+  delfunc Listener
+  unlet g:listener_called
+endfunc
+
+" When multiple listeners are registered, remove one listener and verify the
+" other listener is still called
+func Test_remove_one_listener_in_callback()
+  new
+  let g:listener1_called = 0
+  let g:listener2_called = 0
+  let s:ID1 = listener_add('Listener1')
+  let s:ID2 = listener_add('Listener2')
+  func Listener1(...)
+    call listener_remove(s:ID1)
+    let g:listener1_called += 1
+  endfunc
+  func Listener2(...)
+    let g:listener2_called += 1
+  endfunc
+  call setline(1, ['foo'])
+  call feedkeys("~", 'xt')
+  call listener_flush()
+  call feedkeys("~", 'xt')
+  call listener_flush()
+  call assert_equal(1, g:listener1_called)
+  call assert_equal(2, g:listener2_called)
+
+  call listener_remove(s:ID2)
+  bwipe!
+  delfunc Listener1
+  delfunc Listener2
+  unlet g:listener1_called
+  unlet g:listener2_called
+endfunc
+
+func Test_no_change_for_empty_undo()
+  new
+  let text = ['some word here', 'second line']
+  call setline(1, text)
+  let g:entries = []
+  func Listener(bufnr, start, end, added, changes)
+    for change in a:changes
+      call add(g:entries, [change.lnum, change.end, change.added])
+    endfor
+  endfunc
+  let s:ID = listener_add('Listener')
+  let @a = "one line\ntwo line\nthree line"
+  set undolevels&  " start new undo block
+  call feedkeys('fwviw"ap', 'xt')
+  call listener_flush(bufnr())
+  " first change deletes "word", second change inserts the register
+  call assert_equal([[1, 2, 0], [1, 2, 2]], g:entries)
+  let g:entries = []
+
+  set undolevels&  " start new undo block
+  undo
+  call listener_flush(bufnr())
+  call assert_equal([[1, 4, -2]], g:entries)
+  call assert_equal(text, getline(1, 2))
+
+  call listener_remove(s:ID)
+  bwipe!
+  unlet g:entries
+  delfunc Listener
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab