" Tests for Vim buffer

" Test for the :bunload command with an offset
func Test_bunload_with_offset()
  %bwipe!
  call writefile(['B1'], 'Xb1', 'D')
  call writefile(['B2'], 'Xb2', 'D')
  call writefile(['B3'], 'Xb3', 'D')
  call writefile(['B4'], 'Xb4', 'D')

  " Load four buffers. Unload the second and third buffers and then
  " execute .+3bunload to unload the last buffer.
  edit Xb1
  new Xb2
  new Xb3
  new Xb4

  bunload Xb2
  bunload Xb3
  exe bufwinnr('Xb1') . 'wincmd w'
  .+3bunload
  call assert_equal(0, getbufinfo('Xb4')[0].loaded)
  call assert_equal('Xb1',
        \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))

  " Load four buffers. Unload the third and fourth buffers. Execute .+3bunload
  " and check whether the second buffer is unloaded.
  ball
  bunload Xb3
  bunload Xb4
  exe bufwinnr('Xb1') . 'wincmd w'
  .+3bunload
  call assert_equal(0, getbufinfo('Xb2')[0].loaded)
  call assert_equal('Xb1',
        \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))

  " Load four buffers. Unload the second and third buffers and from the last
  " buffer execute .-3bunload to unload the first buffer.
  ball
  bunload Xb2
  bunload Xb3
  exe bufwinnr('Xb4') . 'wincmd w'
  .-3bunload
  call assert_equal(0, getbufinfo('Xb1')[0].loaded)
  call assert_equal('Xb4',
        \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))

  " Load four buffers. Unload the first and second buffers. Execute .-3bunload
  " from the last buffer and check whether the third buffer is unloaded.
  ball
  bunload Xb1
  bunload Xb2
  exe bufwinnr('Xb4') . 'wincmd w'
  .-3bunload
  call assert_equal(0, getbufinfo('Xb3')[0].loaded)
  call assert_equal('Xb4',
        \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))

  %bwipe!

  call assert_fails('1,4bunload', 'E16:')
  call assert_fails(',100bunload', 'E16:')

  call assert_fails('$bunload', 'E90:')
endfunc

" Test for :buffer, :bnext, :bprevious, :brewind, :blast and :bmodified
" commands
func Test_buflist_browse()
  %bwipe!
  call assert_fails('buffer 1000', 'E86:')

  call writefile(['foo1', 'foo2', 'foo3', 'foo4'], 'Xbrowse1', 'D')
  call writefile(['bar1', 'bar2', 'bar3', 'bar4'], 'Xbrowse2', 'D')
  call writefile(['baz1', 'baz2', 'baz3', 'baz4'], 'Xbrowse3', 'D')
  edit Xbrowse1
  let b1 = bufnr()
  edit Xbrowse2
  let b2 = bufnr()
  edit +/baz4 Xbrowse3
  let b3 = bufnr()

  call assert_fails('buffer ' .. b1 .. ' abc', 'E488:')
  call assert_equal(b3, bufnr())
  call assert_equal(4, line('.'))
  exe 'buffer +/bar2 ' .. b2
  call assert_equal(b2, bufnr())
  call assert_equal(2, line('.'))
  exe 'buffer +/bar1'
  call assert_equal(b2, bufnr())
  call assert_equal(1, line('.'))

  brewind +
  call assert_equal(b1, bufnr())
  call assert_equal(4, line('.'))

  blast +/baz2
  call assert_equal(b3, bufnr())
  call assert_equal(2, line('.'))

  bprevious +/bar4
  call assert_equal(b2, bufnr())
  call assert_equal(4, line('.'))

  bnext +/baz3
  call assert_equal(b3, bufnr())
  call assert_equal(3, line('.'))

  call assert_fails('bmodified', 'E84:')
  call setbufvar(b2, '&modified', 1)
  exe 'bmodified +/bar3'
  call assert_equal(b2, bufnr())
  call assert_equal(3, line('.'))

  " With no listed buffers in the list, :bnext and :bprev should fail
  %bwipe!
  set nobuflisted
  call assert_fails('bnext', 'E85:')
  call assert_fails('bprev', 'E85:')
  set buflisted

  call assert_fails('sandbox bnext', 'E48:')

  %bwipe!
endfunc

" Test for :bnext and :bprev when called from help and non-help buffers.
func Test_bnext_bprev_help()
  %bwipe!

  e XHelp1 | set bt=help
  let b1 = bufnr()
  e Xbuf1
  let b2 = bufnr()

  " There's only one buffer of each type.
  b XHelp1
  bnext | call assert_equal(b1, bufnr())
  bprev | call assert_equal(b1, bufnr())
  b Xbuf1
  bnext | call assert_equal(b2, bufnr())
  bprev | call assert_equal(b2, bufnr())

  " Add one more buffer of each type.
  e XHelp2 | set bt=help
  let b3 = bufnr()
  e Xbuf2
  let b4 = bufnr()

  " Help buffer jumps to help buffer.
  b XHelp1
  bnext | call assert_equal(b3, bufnr())
  bnext | call assert_equal(b1, bufnr())
  bprev | call assert_equal(b3, bufnr())
  bprev | call assert_equal(b1, bufnr())

  " Regular buffer jumps to regular buffer.
  b Xbuf1
  bnext | call assert_equal(b4, bufnr())
  bnext | call assert_equal(b2, bufnr())
  bprev | call assert_equal(b4, bufnr())
  bprev | call assert_equal(b2, bufnr())

  " :brewind and :blast are not affected by the buffer type.
  b Xbuf2
  brewind | call assert_equal(b1, bufnr())
  b XHelp1
  blast   | call assert_equal(b4, bufnr())

  " Cycling through help buffers works even if they aren't listed.
  b XHelp1
  setlocal nobuflisted
  bnext | call assert_equal(b3, bufnr())
  bnext | call assert_equal(b1, bufnr())
  bprev | call assert_equal(b3, bufnr())
  setlocal nobuflisted
  bprev | call assert_equal(b1, bufnr())
  bprev | call assert_equal(b3, bufnr())
  bnext | call assert_equal(b1, bufnr())

  %bwipe!
endfunc

" Test for :bdelete
func Test_bdelete_cmd()
  %bwipe!
  call assert_fails('bdelete 5', 'E516:')
  call assert_fails('1,1bdelete 1 2', 'E488:')
  call assert_fails('bdelete \)', 'E55:')

  " Deleting an unlisted and unloaded buffer
  edit Xbdelfile1
  let bnr = bufnr()
  set nobuflisted
  enew
  call assert_fails('bdelete ' .. bnr, 'E516:')

  " Deleting more than one buffer
  new Xbuf1
  new Xbuf2
  exe 'bdel ' .. bufnr('Xbuf2') .. ' ' .. bufnr('Xbuf1')
  call assert_equal(1, winnr('$'))
  call assert_equal(0, getbufinfo('Xbuf1')[0].loaded)
  call assert_equal(0, getbufinfo('Xbuf2')[0].loaded)

  " Deleting more than one buffer and an invalid buffer
  new Xbuf1
  new Xbuf2
  let cmd = "exe 'bdel ' .. bufnr('Xbuf2') .. ' xxx ' .. bufnr('Xbuf1')"
  call assert_fails(cmd, 'E94:')
  call assert_equal(2, winnr('$'))
  call assert_equal(1, getbufinfo('Xbuf1')[0].loaded)
  call assert_equal(0, getbufinfo('Xbuf2')[0].loaded)

  %bwipe!
endfunc

func Test_buffer_error()
  new foo1
  new foo2

  call assert_fails('buffer foo', 'E93:')
  call assert_fails('buffer bar', 'E94:')
  call assert_fails('buffer 0', 'E939:')

  %bwipe
endfunc

func Test_bwipe_during_save()
  set charconvert=execute('%bw!')
  call assert_fails('write ++enc=lmao boom', 'E937:')

  set charconvert&
  %bwipe
endfunc

" Test for the status messages displayed when unloading, deleting or wiping
" out buffers
func Test_buffer_statusmsg()
  CheckEnglish
  set report=1
  new Xbuf1
  new Xbuf2
  let bnr = bufnr()
  exe "normal 2\<C-G>"
  call assert_match('buf ' .. bnr .. ':', v:statusmsg)
  bunload Xbuf1 Xbuf2
  call assert_equal('2 buffers unloaded', v:statusmsg)
  bdel Xbuf1 Xbuf2
  call assert_equal('2 buffers deleted', v:statusmsg)
  bwipe Xbuf1 Xbuf2
  call assert_equal('2 buffers wiped out', v:statusmsg)
  set report&
endfunc

" Test for quitting the 'swapfile exists' dialog with the split buffer
" command.
func Test_buffer_sbuf_cleanup()
  call writefile([], 'XsplitCleanup', 'D')
  " first open the file in a buffer
  new XsplitCleanup
  let bnr = bufnr()
  close
  " create the swap file
  call writefile([], '.XsplitCleanup.swp', 'D')
  " Remove the catch-all that runtest.vim adds
  au! SwapExists
  augroup BufTest
    au!
    autocmd SwapExists XsplitCleanup let v:swapchoice='q'
  augroup END
  exe 'sbuf ' . bnr
  call assert_equal(1, winnr('$'))
  call assert_equal(0, getbufinfo('XsplitCleanup')[0].loaded)

  " test for :sball
  sball
  call assert_equal(1, winnr('$'))
  call assert_equal(0, getbufinfo('XsplitCleanup')[0].loaded)

  %bw!
  set shortmess+=F
  let v:statusmsg = ''
  edit XsplitCleanup
  call assert_equal('', v:statusmsg)
  call assert_equal(1, winnr('$'))
  call assert_equal(0, getbufinfo('XsplitCleanup')[0].loaded)
  set shortmess&

  augroup BufTest
    au!
  augroup END
  augroup! BufTest
endfunc

" Test for deleting a modified buffer with :confirm
func Test_bdel_with_confirm()
  CheckUnix
  CheckNotGui
  CheckFeature dialog_con
  new
  call setline(1, 'test')
  call assert_fails('bdel', 'E89:')
  call feedkeys('c', 'L')
  confirm bdel
  call assert_equal(2, winnr('$'))
  call assert_equal(1, &modified)
  call feedkeys('n', 'L')
  confirm bdel
  call assert_equal(1, winnr('$'))
endfunc

" Test for editing another buffer from a modified buffer with :confirm
func Test_goto_buf_with_confirm()
  CheckUnix
  CheckNotGui
  CheckFeature dialog_con
  " When dialog_con_gui is defined, Vim is compiled with GUI support
  " and FEAT_BROWSE will be defined, which causes :confirm :b to
  " call do_browse(), which will try to use a GUI file browser,
  " which aborts if a GUI is not available.
  CheckNotFeature dialog_con_gui
  new XgotoConf
  enew
  call setline(1, 'test')
  call assert_fails('b XgotoConf', 'E37:')
  call feedkeys('c', 'L')
  call assert_fails('confirm b XgotoConf', 'E37:')
  call assert_true(&modified)
  call assert_true(empty(bufname('%')))
  call feedkeys('y', 'L')
  confirm b XgotoConf
  call assert_equal('XgotoConf', bufname('%'))
  call assert_equal(['test'], readfile('Untitled'))
  e Untitled
  call setline(2, 'test2')
  call feedkeys('n', 'L')
  confirm b XgotoConf
  call assert_equal('XgotoConf', bufname('%'))
  call assert_equal(['test'], readfile('Untitled'))
  call delete('Untitled')
  close!
endfunc

" Test for issue #20132: when saving an unnamed buffer fails the modified
" flag must be kept, otherwise the buffer is silently discarded.
func Test_dialog_changed_keep_modified_on_write_fail()
  CheckUnix
  CheckNotGui
  CheckFeature dialog_con
  CheckNotFeature dialog_con_gui
  CheckNotRoot

  call writefile(['existing'], 'Untitled', 'D')
  call setfperm('Untitled', 'r--r--r--')

  new
  call setline(1, 'test')
  call feedkeys('y', 'L')
  silent! confirm bdel
  call assert_true(&modified)
  call assert_equal(['existing'], readfile('Untitled'))

  bw!
endfunc

" Test for splitting buffer with 'switchbuf'
func Test_buffer_switchbuf()
  new Xswitchbuf
  wincmd w
  set switchbuf=useopen
  sbuf Xswitchbuf
  call assert_equal(1, winnr())
  call assert_equal(2, winnr('$'))
  set switchbuf=usetab
  tabnew
  sbuf Xswitchbuf
  call assert_equal(1, tabpagenr())
  call assert_equal(2, tabpagenr('$'))
  set switchbuf&
  %bw
endfunc

" Test for BufAdd autocommand wiping out the buffer
func Test_bufadd_autocmd_bwipe()
  %bw!
  augroup BufAdd_Wipe
    au!
    autocmd BufAdd Xbwipe %bw!
  augroup END
  edit Xbwipe
  call assert_equal('', @%)
  call assert_equal(0, bufexists('Xbwipe'))
  augroup BufAdd_Wipe
    au!
  augroup END
  augroup! BufAdd_Wipe
endfunc

" Test for trying to load a buffer with text locked
" <C-\>e in the command line is used to lock the text
func Test_load_buf_with_text_locked()
  new Xlockfile1
  edit Xlockfile2
  let cmd = ":\<C-\>eexecute(\"normal \<C-O>\")\<CR>\<C-C>"
  call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
  %bw!
endfunc

" Test for using CTRL-^ to edit the alternative file keeping the cursor
" position with 'nostartofline'. Also test using the 'buf' command.
func Test_buffer_edit_altfile()
  call writefile(repeat(['one two'], 50), 'Xaltfile1', 'D')
  call writefile(repeat(['five six'], 50), 'Xaltfile2', 'D')
  set nosol
  edit Xaltfile1
  call cursor(25, 5)
  edit Xaltfile2
  call cursor(30, 4)
  exe "normal \<C-^>"
  call assert_equal([0, 25, 5, 0], getpos('.'))
  exe "normal \<C-^>"
  call assert_equal([0, 30, 4, 0], getpos('.'))
  buf Xaltfile1
  call assert_equal([0, 25, 5, 0], getpos('.'))
  buf Xaltfile2
  call assert_equal([0, 30, 4, 0], getpos('.'))
  set sol&
endfunc

" Test for running the :sball command with a maximum window count and a
" modified buffer
func Test_sball_with_count()
  %bw!
  edit Xcountfile1
  call setline(1, ['abc'])
  new Xcountfile2
  new Xcountfile3
  new Xcountfile4
  2sball
  call assert_equal(bufnr('Xcountfile4'), winbufnr(1))
  call assert_equal(bufnr('Xcountfile1'), winbufnr(2))
  call assert_equal(0, getbufinfo('Xcountfile2')[0].loaded)
  call assert_equal(0, getbufinfo('Xcountfile3')[0].loaded)
  %bw!
endfunc

func Test_badd_options()
  new SomeNewBuffer
  setlocal numberwidth=3
  wincmd p
  badd +1 SomeNewBuffer
  new SomeNewBuffer
  call assert_equal(3, &numberwidth)
  close
  close
  bwipe! SomeNewBuffer

  badd +3 XbaddFile
  call writefile(range(6), 'XbaddFile', 'D')
  buf XbaddFile
  call assert_equal([0, 3, 1, 0], getpos('.'))

  bwipe! XbaddFile
endfunc

func Test_balt()
  new SomeNewBuffer
  balt +3 OtherBuffer
  e #
  call assert_equal('OtherBuffer', bufname())
endfunc

" Test for buffer match URL(scheme) check
" scheme is alpha and inner hyphen only.
func Test_buffer_scheme()
  CheckMSWindows

  set noswapfile
  set noshellslash
  %bwipe!
  let bufnames = [
    \ #{id: 'ssb0', name: 'test://xyz/foo/ssb0'    , match: 1},
    \ #{id: 'ssb1', name: 'test+abc://xyz/foo/ssb1', match: 0},
    \ #{id: 'ssb2', name: 'test_abc://xyz/foo/ssb2', match: 0},
    \ #{id: 'ssb3', name: 'test-abc://xyz/foo/ssb3', match: 1},
    \ #{id: 'ssb4', name: '-test://xyz/foo/ssb4'   , match: 0},
    \ #{id: 'ssb5', name: 'test-://xyz/foo/ssb5'   , match: 0},
    \]
  for buf in bufnames
    new `=buf.name`
    if buf.match
      call assert_equal(buf.name,    getbufinfo(buf.id)[0].name)
    else
      " slashes will have become backslashes
      call assert_notequal(buf.name, getbufinfo(buf.id)[0].name)
    endif
    bwipe
  endfor

  set shellslash&
  set swapfile&
endfunc

" this was using a NULL pointer after failing to use the pattern
func Test_buf_pattern_invalid()
  vsplit 0000000
  silent! buf [0--]\&\zs*\zs*e
  bwipe!

  vsplit 00000000000000000000000000
  silent! buf [0--]\&\zs*\zs*e
  bwipe!

  " similar case with different code path
  split 0
  edit ÿ
  silent! buf [0--]\&\zs*\zs*0
  bwipe!
endfunc

" Test for the 'maxmem' and 'maxmemtot' options
func Test_buffer_maxmem()
  " use 1KB per buffer and 2KB for all the buffers
  set maxmem=1 maxmemtot=2
  new
  let v:errmsg = ''
  " try opening some files
  edit test_arglist.vim
  call assert_equal('test_arglist.vim', bufname())
  edit test_eval_stuff.vim
  call assert_equal('test_eval_stuff.vim', bufname())
  b test_arglist.vim
  call assert_equal('test_arglist.vim', bufname())
  b test_eval_stuff.vim
  call assert_equal('test_eval_stuff.vim', bufname())
  close
  call assert_equal('', v:errmsg)
  set maxmem& maxmemtot&
endfunc

" Test for buffer allocation failure
func Test_buflist_alloc_failure()
  %bw!

  edit XallocFail1
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails('edit XallocFail2', 'E342:')

  " test for bufadd()
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails('call bufadd("Xbuffer")', 'E342:')

  " test for setting the arglist
  edit XallocFail2
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails('next XallocFail3', 'E342:')

  " test for setting the alternate buffer name when writing a file
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails('write Xother', 'E342:')
  call delete('Xother')

  " test for creating a buffer using bufnr()
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails("call bufnr('Xnewbuf', v:true)", 'E342:')

  " test for renaming buffer using :file
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails('file Xnewfile', 'E342:')

  " test for creating a buffer for a popup window
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails('call popup_create("mypop", {})', 'E342:')

  if has('terminal')
    " test for creating a buffer for a terminal window
    call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
    call assert_fails('call term_start(&shell)', 'E342:')
    %bw!
  endif

  " test for loading a new buffer after wiping out all the buffers
  edit XallocFail4
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails('%bw!', 'E342:')

  " test for :checktime loading the buffer
  call writefile(['one'], 'XallocFail5', 'D')
  if has('unix')
    edit XallocFail5
    " sleep for some time to make sure the timestamp is different
    sleep 200m
    call writefile(['two'], 'XallocFail5')
    set autoread
    call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
    call assert_fails('checktime', 'E342:')
    set autoread&
    bw!
  endif

  " test for :vimgrep loading a dummy buffer
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails('vimgrep two XallocFail5', 'E342:')

  " test for quickfix command loading a buffer
  call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
  call assert_fails('cexpr "XallocFail6:10:Line10"', 'E342:')
endfunc

func Test_closed_buffer_still_in_window()
  %bw!

  let s:fired = 0
  let s:w = win_getid()
  new
  let s:b = bufnr()
  setl bufhidden=wipe
  augroup ViewClosedBuffer
    autocmd!
    autocmd BufUnload * ++once let s:fired += 1 | call assert_fails(
          \ 'call win_execute(s:w, "' .. s:b .. 'buffer")', 'E1546:')
  augroup END
  quit!
  " Previously resulted in s:b being curbuf while unloaded (no memfile).
  call assert_equal(1, s:fired)
  call assert_equal(1, bufloaded(bufnr()))
  call assert_equal(0, bufexists(s:b))
  %bw!

  new flobby
  let s:w = win_getid()
  let s:b = bufnr()
  setl bufhidden=wipe
  augroup ViewClosedBuffer
    autocmd!
    autocmd BufUnload * ++once let s:fired += 1
          \| wincmd p
          \| call assert_notequal(s:w, win_getid())
          \| call assert_notequal(s:b, bufnr())
          \| execute s:b 'sbuffer'
          \| call assert_equal(s:w, win_getid())
          \| call assert_equal(s:b, bufnr())
  augroup END
  " Not a problem if 'switchbuf' switches to an existing window instead.
  set switchbuf=useopen
  quit!
  call assert_equal(2, s:fired)
  call assert_equal(0, bufexists(s:b))
  set switchbuf&
  %bw!

  edit floob
  let s:b = bufnr()
  enew
  augroup ViewClosedBuffer
    autocmd!
    autocmd BufWipeout * ++once let s:fired += 1
          \| call assert_fails(s:b .. 'sbuffer | wincmd p', 'E1546:')
          \| call assert_equal(1, winnr('$')) " :sbuffer shouldn't have split.
  augroup END
  " Used to be a heap UAF.
  execute s:b 'bwipeout!'
  call assert_equal(3, s:fired)
  call assert_equal(0, bufexists(s:b))
  %bw!

  edit flarb
  let s:b = bufnr()
  enew
  let b2 = bufnr()
  augroup ViewClosedBuffer
    autocmd!
    autocmd BufWipeout * ++once let s:fired += 1
          \| call assert_fails(s:b .. 'sbuffer', 'E1159:')
  augroup END
  " :sbuffer still should fail if curbuf is closing, even if it's not the target
  " buffer (as switching buffers can fail after the split)
  bwipeout!
  call assert_equal(4, s:fired)
  call assert_equal(0, bufexists(b2))
  %bw!

  let s:w = win_getid()
  split
  new
  let s:b = bufnr()
  augroup ViewClosedBuffer
    autocmd!
    autocmd BufWipeout * ++once let s:fired += 1 | call win_gotoid(s:w)
          \| call assert_fails(s:b .. 'buffer', 'E1546:') | wincmd p
  augroup END
  bw! " Close only this buffer first; used to be a heap UAF.
  call assert_equal(5, s:fired)

  unlet! s:w s:b s:fired
  autocmd! ViewClosedBuffer
  %bw!
endfunc

" Cursor position should be restored when switching to a buffer previously
" viewed in a window, regardless of whether it's visible in another one.
func Test_switch_to_previously_viewed_buffer()
  set nostartofline
  new Xviewbuf
  call setline(1, range(1, 200))
  let oldwin = win_getid()
  vsplit

  call cursor(100, 3)
  call assert_equal('100', getline('.'))
  edit Xotherbuf
  buffer Xviewbuf
  call assert_equal([0, 100, 3, 0], getpos('.'))
  call assert_equal('100', getline('.'))

  edit Xotherbuf
  wincmd p
  normal! gg10dd
  wincmd p
  buffer Xviewbuf
  call assert_equal([0, 90, 3, 0], getpos('.'))
  call assert_equal('100', getline('.'))

  edit Xotherbuf
  wincmd p
  normal! ggP
  wincmd p
  buffer Xviewbuf
  call assert_equal([0, 100, 3, 0], getpos('.'))
  call assert_equal('100', getline('.'))

  edit Xotherbuf
  wincmd p
  normal! 96gg10ddgg
  wincmd p
  buffer Xviewbuf
  " The original cursor line was deleted, so cursor is restored to the start
  " of the line before the deleted range.
  call assert_equal([0, 95, 1, 0], getpos('.'))
  call assert_equal('95', getline('.'))

  normal! u
  exe win_id2win(oldwin) .. 'close'
  setlocal bufhidden=hide

  call cursor(200, 3)
  call assert_equal('200', getline('.'))
  edit Xotherbuf
  buffer Xviewbuf
  call assert_equal([0, 200, 3, 0], getpos('.'))
  call assert_equal('200', getline('.'))

  bwipe! Xotherbuf
  bwipe! Xviewbuf
  set startofline&
endfunc

func Test_bdelete_skip_closing_bufs()
  set hidden
  let s:fired = 0

  edit foo
  edit bar
  let s:next_new_bufnr = bufnr('$') + 1
  augroup SkipClosing
    autocmd!
    " Only window and other buffer is closing.
    " No choice but to switch to a new, empty buffer.
    autocmd BufDelete * ++once let s:fired += 1
          \| call assert_equal(1, winnr('$'))
          \| call assert_equal('bar', bufname())
          \| bdelete
          \| call assert_equal('', bufname())
          \| call assert_equal(s:next_new_bufnr, bufnr())
  augroup END
  bdelete foo
  call assert_equal(1, s:fired)
  unlet! s:next_new_bufnr
  %bw!

  edit baz
  edit bar
  edit fleb
  edit foo
  augroup SkipClosing
    autocmd!
    " Only window, au_new_curbuf is NOT closing; should end up there.
    autocmd BufDelete * ++once let s:fired += 1
          \| call assert_equal(1, winnr('$'))
          \| call assert_equal('foo', bufname())
          \| bwipeout
          \| call assert_equal('bar', bufname())
  augroup END
  buffer baz
  buffer foo
  augroup SkipClosing
    autocmd BufLeave * ++once ++nested bdelete baz
  augroup END
  edit bar
  call assert_equal(2, s:fired)
  %bw!

  edit baz
  edit bar
  edit fleb
  edit foo
  augroup SkipClosing
    autocmd!
    " Like above, but au_new_curbuf IS closing.
    " Should use the most recent jumplist buffer instead.
    autocmd BufDelete * ++once let s:fired += 1
          \| call assert_equal(1, winnr('$'))
          \| call assert_equal('foo', bufname())
          \| bwipeout
          \| call assert_equal('baz', bufname())
  augroup END
  buffer baz
  buffer foo
  augroup SkipClosing
    autocmd BufLeave * ++once ++nested bdelete bar
  augroup END
  edit bar
  call assert_equal(3, s:fired)
  %bw!

  edit foo
  edit floob
  edit baz
  edit bar
  augroup SkipClosing
    autocmd!
    " Only window, most recent buffer in jumplist is closing.
    " Should switch to the next most-recent buffer in the jumplist instead.
    autocmd BufDelete * ++once let s:fired += 1
          \| call assert_equal(1, winnr('$'))
          \| call assert_equal('bar', bufname())
          \| bdelete
          \| call assert_equal('floob', bufname())
  augroup END
  buffer baz
  buffer floob
  buffer foo
  buffer bar
  bdelete foo
  call assert_equal(4, s:fired)
  %bw!

  edit foo
  edit baz
  edit bar
  edit floob
  edit bazinga
  augroup SkipClosing
    autocmd!
    " Only window, most recent jumplist buffer is gone, next most-recent is
    " closing.  Should switch to the 3rd most-recent jumplist buffer.
    autocmd BufDelete * ++once let s:fired += 1
          \| call assert_equal(1, winnr('$'))
          \| call assert_equal('bar', bufname())
          \| bwipeout
          \| call assert_equal('baz', bufname())
  augroup END
  buffer bazinga
  buffer baz
  buffer floob
  buffer foo
  buffer bar
  noautocmd bdelete foo
  bdelete floob
  call assert_equal(5, s:fired)
  %bw!

  edit foo
  edit baz
  edit floob
  edit bazinga
  edit bar
  augroup SkipClosing
    autocmd!
    " Like above, but jumplist cleared, no next buffer in the buffer list and
    " previous buffer is closing.  Should switch to the buffer before previous.
    autocmd BufDelete * ++once let s:fired += 1
          \| call assert_equal(1, winnr('$'))
          \| call assert_equal('bar', bufname())
          \| bunload
          \| call assert_equal('floob', bufname())
  augroup END
  buffer bazinga
  buffer baz
  buffer floob
  buffer foo
  buffer bar
  noautocmd bdelete foo
  clearjumps
  bdelete bazinga
  call assert_equal(6, s:fired)

  unlet! s:fired
  autocmd! SkipClosing
  set hidden&
  %bw!
endfunc

func Test_split_window_in_BufLeave_from_tab_sbuffer()
  tabnew Xa
  setlocal bufhidden=wipe
  let t0 = tabpagenr()
  let b0 = bufnr()
  let b1 = bufadd('Xb')
  autocmd BufLeave Xa ++once split
  exe 'tab sbuffer' b1
  call assert_equal(t0 + 1, tabpagenr())
  call assert_equal([b1, b0], tabpagebuflist())
  call assert_equal([b0], tabpagebuflist(t0))
  tabclose
  call assert_equal(t0, tabpagenr())
  call assert_equal([b0], tabpagebuflist())

  bwipe! Xa
  bwipe! Xb
endfunc

func Test_split_window_in_BufLeave_from_switching_buffer()
  tabnew Xa
  setlocal bufhidden=wipe
  split
  let b0 = bufnr()
  let b1 = bufadd('Xb')
  autocmd BufLeave Xa ++once split
  exe 'buffer' b1
  call assert_equal([b1, b0, b0], tabpagebuflist())

  bwipe! Xa
  bwipe! Xb
endfunc

func Test_wipe_other_buffers_in_WinLeave_from_bwipe()
  tabnew
  autocmd WinLeave * ++once 1,$-1bwipe!
  " This should not crash
  $bwipe!
endfunc

" Switch to a buffer whose name contains '%' via completion (#20529).
func Test_buffer_switch_to_name_with_percent()
  CheckMSWindows

  let bufnr = bufadd('Xpercent%name')
  call setbufvar(bufnr, '&buflisted', 1)
  call bufload(bufnr)
  enew

  call feedkeys(":b Xpercent\<Tab>\<CR>", 'xt')
  call assert_equal(bufnr, bufnr('%'))

  exe 'bwipe! ' .. bufnr
endfunc

" vim: shiftwidth=2 sts=2 expandtab
