Before:
  Save g:ale_completion_delay
  Save g:ale_completion_enabled
  Save g:ale_completion_max_suggestions
  Save g:ale_completion_info
  Save g:ale_completion_autoimport
  Save &l:omnifunc
  Save &l:completeopt

  let g:ale_completion_enabled = v:true
  let g:ale_completion_autoimport = v:true

  call ale#test#SetDirectory('/testplugin/test/completion')
  call ale#test#SetFilename('dummy.txt')

  runtime autoload/ale/lsp.vim

  let g:message_list = []
  let g:capability_checked = ''
  let g:conn_id = v:null
  let g:Callback = ''
  let g:init_callback_list = []

  function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
    let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
    call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)

    let l:details = {
    \ 'command': 'foobar',
    \ 'buffer': a:buffer,
    \ 'connection_id': g:conn_id,
    \ 'project_root': '/foo/bar',
    \}

    call add(g:init_callback_list, {-> a:Callback(a:linter, l:details)})
  endfunction

  " Pretend we're in insert mode for most tests.
  function! ale#util#Mode(...) abort
    return 'i'
  endfunction

  function! ale#lsp#HasCapability(conn_id, capability) abort
    let g:capability_checked = a:capability

    return 1
  endfunction

  function! ale#lsp#RegisterCallback(conn_id, callback) abort
    let g:Callback = a:callback
  endfunction

  " Replace the Send function for LSP, so we can monitor calls to it.
  function! ale#lsp#Send(conn_id, message) abort
    call add(g:message_list, a:message)

    return 1
  endfunction

After:
  Restore

  if g:conn_id isnot v:null
    call ale#lsp#RemoveConnectionWithID(g:conn_id)
  endif

  unlet! g:message_list
  unlet! g:capability_checked
  unlet! g:init_callback_list
  unlet! g:conn_id
  unlet! g:Callback
  unlet! b:ale_old_omnifunc
  unlet! b:ale_old_completeopt
  unlet! b:ale_completion_info
  unlet! b:ale_complete_done_time
  unlet! b:ale_linters
  unlet! b:ale_tsserver_completion_names

  " Reset the function.
  function! ale#util#Mode(...) abort
    return call('mode', a:000)
  endfunction

  call ale#test#RestoreDirectory()
  call ale#linter#Reset()

  " Stop any timers we left behind.
  " This stops the tests from failing randomly.
  call ale#completion#StopTimer()

  runtime autoload/ale/completion.vim
  runtime autoload/ale/lsp.vim
  runtime autoload/ale/lsp_linter.vim

Given typescript(Some typescript file):
  foo
  somelongerline
  bazxyzxyzxyz

Execute(The right message should be sent for the initial tsserver request):
  runtime ale_linters/typescript/tsserver.vim
  let b:ale_linters = ['tsserver']
  " The cursor position needs to match what was saved before.
  call setpos('.', [bufnr(''), 1, 3, 0])

  call ale#completion#GetCompletions('ale-automatic')

  " We shouldn't register the callback yet.
  AssertEqual '''''', string(g:Callback)

  AssertEqual 1, len(g:init_callback_list)
  call map(g:init_callback_list, 'v:val()')

  AssertEqual 'completion', g:capability_checked

  " We should send the right callback.
  AssertEqual
  \ 'function(''ale#completion#HandleTSServerResponse'')',
  \ string(g:Callback)
  " We should send the right message.
  AssertEqual
  \ [[0, 'ts@completions', {
  \  'file': expand('%:p'),
  \  'line': 1,
  \  'offset': 3,
  \  'prefix': 'fo',
  \  'includeExternalModuleExports': g:ale_completion_autoimport,
  \ }]],
  \ g:message_list
  " We should set up the completion info correctly.
  AssertEqual
  \ {
  \   'line_length': 3,
  \   'conn_id': g:conn_id,
  \   'column': 3,
  \   'request_id': 1,
  \   'line': 1,
  \   'prefix': 'fo',
  \   'source': 'ale-automatic',
  \ },
  \ get(b:, 'ale_completion_info', {})

Execute(The right message sent to the tsserver LSP when the first completion message is received):
  " The cursor position needs to match what was saved before.
  call setpos('.', [bufnr(''), 1, 1, 0])
  let b:ale_completion_info = {
  \ 'conn_id': 123,
  \ 'prefix': 'f',
  \ 'request_id': 4,
  \ 'line': 1,
  \ 'column': 1,
  \}
  " We should only show up to this many suggestions.
  let g:ale_completion_max_suggestions = 3

  " Handle the response for completions.
  call ale#completion#HandleTSServerResponse(123, {
  \ 'request_seq': 4,
  \ 'command': 'completions',
  \ 'body': [
  \   {'name': 'Baz'},
  \   {'name': 'dingDong'},
  \   {'name': 'Foo', 'source': '/path/to/foo.ts'},
  \   {'name': 'FooBar'},
  \   {'name': 'frazzle'},
  \   {'name': 'FFS'},
  \ ],
  \})

  " We should save the names we got in the buffer, as TSServer doesn't return
  " details for every name.
  AssertEqual [{
  \  'word': 'Foo',
  \  'source': '/path/to/foo.ts',
  \ }, {
  \  'word': 'FooBar',
  \  'source': '',
  \ }, {
  \  'word': 'frazzle',
  \  'source': '',
  \}],
  \ get(b:, 'ale_tsserver_completion_names', [])

  " The entry details messages should have been sent.
  AssertEqual
  \ [[
  \   0,
  \   'ts@completionEntryDetails',
  \   {
  \     'file': expand('%:p'),
  \     'entryNames': [{
  \          'name': 'Foo',
  \          'source': '/path/to/foo.ts',
  \         }, {
  \          'name': 'FooBar',
  \         }, {
  \          'name': 'frazzle',
  \     }],
  \     'offset': 1,
  \     'line': 1,
  \   },
  \ ]],
  \ g:message_list

Given python(Some Python file):
  foo
  somelongerline
  bazxyzxyzxyz

Execute(The right message should be sent for the initial LSP request):
  runtime ale_linters/python/pylsp.vim
  let b:ale_linters = ['pylsp']
  " The cursor position needs to match what was saved before.
  call setpos('.', [bufnr(''), 1, 5, 0])

  call ale#completion#GetCompletions('ale-automatic')

  " We shouldn't register the callback yet.
  AssertEqual '''''', string(g:Callback)

  AssertEqual 1, len(g:init_callback_list)
  call map(g:init_callback_list, 'v:val()')

  AssertEqual 'completion', g:capability_checked

  " We should send the right callback.
  AssertEqual
  \ 'function(''ale#completion#HandleLSPResponse'')',
  \ string(g:Callback)
  " We should send the right message.
  " The character index needs to be at most the index of the last character on
  " the line, or integration with pylsp will be broken.
  "
  " We need to send the message for changing the document first.
  AssertEqual
  \ [
  \   [1, 'textDocument/didChange', {
  \     'textDocument': {
  \         'uri': ale#path#ToFileURI(expand('%:p')),
  \         'version': g:ale_lsp_next_version_id - 1,
  \     },
  \     'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}]
  \   }],
  \   [0, 'textDocument/completion', {
  \   'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))},
  \   'position': {'line': 0, 'character': 2},
  \   }],
  \ ],
  \ g:message_list
  " We should set up the completion info correctly.
  AssertEqual
  \ {
  \   'line_length': 3,
  \   'conn_id': g:conn_id,
  \   'column': 3,
  \   'request_id': 1,
  \   'line': 1,
  \   'prefix': 'fo',
  \   'source': 'ale-automatic',
  \   'completion_filter': 'ale#completion#python#CompletionItemFilter',
  \ },
  \ get(b:, 'ale_completion_info', {})

Execute(Two completion requests shouldn't be sent in a row):
  call ale#linter#PreventLoading('python')
  call ale#linter#Define('python', {
  \   'name': 'foo',
  \   'lsp': 'stdio',
  \   'executable': 'foo',
  \   'command': 'foo',
  \   'project_root': {-> '/foo/bar'},
  \})
  call ale#linter#Define('python', {
  \   'name': 'bar',
  \   'lsp': 'stdio',
  \   'executable': 'foo',
  \   'command': 'foo',
  \   'project_root': {-> '/foo/bar'},
  \})
  let b:ale_linters = ['foo', 'bar']

  " The cursor position needs to match what was saved before.
  call setpos('.', [bufnr(''), 1, 5, 0])

  call ale#completion#GetCompletions('ale-automatic')

  " We shouldn't register the callback yet.
  AssertEqual '''''', string(g:Callback)

  AssertEqual 2, len(g:init_callback_list)
  call map(g:init_callback_list, 'v:val()')

  AssertEqual 'completion', g:capability_checked

  " We should only send one completion message for two LSP servers.
  AssertEqual
  \ [
  \   [1, 'textDocument/didChange', {
  \     'textDocument': {
  \         'uri': ale#path#ToFileURI(expand('%:p')),
  \         'version': g:ale_lsp_next_version_id - 1,
  \     },
  \     'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}]
  \   }],
  \   [0, 'textDocument/completion', {
  \   'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))},
  \   'position': {'line': 0, 'character': 2},
  \   }],
  \ ],
  \ g:message_list
